Maven — Java Build and Dependency Management
Learn Maven — the industry-standard Java build tool for managing dependencies, project structure, build lifecycle, and producing deployable artifacts.
Maven is the most widely used build tool in Java. Without it, you would manually download every JAR your project needs, manage version conflicts by hand, and invent your own build scripts. Maven solves all three problems: it downloads dependencies automatically from Maven Central, enforces a standard project layout that every Java developer recognises, and provides a fixed lifecycle so mvn package always means the same thing across every project.
Installing Maven
Download from maven.apache.org and add bin/ to your PATH, or use your package manager:
# macOS
brew install maven
# Ubuntu/Debian
sudo apt install maven
# Verify
mvn -version
# Apache Maven 3.9.6
IntelliJ IDEA bundles Maven — you don’t need to install it separately for IDE use.
Project Structure Convention
Maven enforces a standard layout. Every Maven project looks the same, which means any Java developer can find the source, tests, and resources without reading a README. You don’t configure this — you follow it.
my-app/
├── pom.xml ← project descriptor (the only config file you need)
└── src/
├── main/
│ ├── java/ ← production source code
│ │ └── com/example/
│ │ └── App.java
│ └── resources/ ← config files, SQL, templates (on the classpath)
│ └── application.properties
└── test/
├── java/ ← test source code (not included in the final JAR)
│ └── com/example/
│ └── AppTest.java
└── resources/ ← test-only config files
pom.xml — The Project Descriptor
The pom.xml is the single source of truth for your project. It declares what your project is, what it depends on, and how to build it. Maven reads this file and figures out everything else.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- GAV coordinates — uniquely identify this artifact in Maven Central -->
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging> <!-- jar (default), war, pom -->
<name>My Application</name>
<description>A sample Maven project</description>
<properties>
<!-- Define versions once, reference with ${property.name} -->
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Production dependency — included in the final JAR -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.1.0-jre</version>
</dependency>
<!-- Test-only dependency — not included in production output -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<!-- Available at compile time, not bundled — provided by the container -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Compiler plugin — explicitly set Java version to avoid surprises -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<release>21</release>
</configuration>
</plugin>
<!-- Surefire — runs JUnit tests during the test phase -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>
</project>
Dependency Management
Maven resolves dependencies from Maven Central (the default public repository). Each dependency is identified by three coordinates — groupId, artifactId, and version — which together uniquely identify one artifact in the entire repository.
<dependency>
<groupId>org.springframework.boot</groupId> <!-- organisation or project -->
<artifactId>spring-boot-starter-web</artifactId> <!-- specific module -->
<version>3.2.5</version> <!-- exact version -->
<scope>compile</scope> <!-- optional: compile (default), test, provided, runtime -->
</dependency>
Dependency Scopes
Scopes control when a dependency is on the classpath and whether it ends up in the packaged artifact. Misusing scopes can include test libraries in production JARs or miss runtime drivers.
| Scope | Classpath | Packaged | Use for |
|---|---|---|---|
compile (default) | compile + runtime | yes | normal dependencies |
test | test only | no | JUnit, Mockito |
provided | compile only | no | Servlet API, Lombok |
runtime | runtime only | yes | JDBC drivers |
Finding Dependencies
Search at mvnrepository.com — copy the <dependency> XML snippet into your pom.xml.
Transitive Dependencies
Maven automatically downloads the dependencies of your dependencies. This is convenient but can introduce version conflicts. To see the full dependency tree and spot conflicts:
mvn dependency:tree
To exclude a transitive dependency you don’t want:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.5</version>
<exclusions>
<exclusion>
<!-- Exclude Tomcat to use Jetty instead -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
Build Lifecycle
Maven’s lifecycle is fixed and sequential. Each phase automatically runs all preceding phases, so mvn package compiles, tests, and packages — you never have to think about the order.
validate → compile → test → package → verify → install → deploy
mvn compile # compile src/main/java → target/classes
mvn test # compile + run all tests
mvn package # compile + test + create JAR/WAR in target/
mvn install # package + copy to local ~/.m2 repository (for other local projects)
mvn clean # delete the target/ directory
mvn clean package # clean then full build — most common command
mvn clean install -DskipTests # skip tests (useful for CI speed, not recommended otherwise)
Common Commands
# Create a new project from the quickstart archetype
mvn archetype:generate \
-DgroupId=com.example \
-DartifactId=my-app \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4
# Run the packaged JAR
java -jar target/my-app-1.0.0.jar
# Show the effective pom — all defaults and inherited settings merged in
mvn help:effective-pom
# Show dependency tree — use to diagnose version conflicts
mvn dependency:tree
# Analyse for unused declared and used undeclared dependencies
mvn dependency:analyze
Multi-Module Projects
Large projects split into modules that share a parent pom. The parent controls shared configuration (Java version, plugin versions, common dependencies) while each module only declares what it needs beyond the baseline.
parent/
├── pom.xml ← parent (packaging: pom)
├── core/
│ ├── pom.xml
│ └── src/...
├── service/
│ ├── pom.xml ← depends on core
│ └── src/...
└── web/
├── pom.xml ← depends on service
└── src/...
Parent pom.xml:
<packaging>pom</packaging>
<modules>
<module>core</module>
<module>service</module>
<module>web</module>
</modules>
<!-- dependencyManagement: define versions once; child modules reference without version -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
Creating an Executable JAR
By default, mvn package creates a thin JAR that does not include dependencies. The Maven Shade Plugin bundles everything into a single executable “fat JAR” that runs anywhere with java -jar.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.2</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<!-- Sets the Main-Class attribute in the JAR manifest -->
<mainClass>com.example.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
mvn clean package
java -jar target/my-app-1.0.0-shaded.jar