JavaBuild Tools: Maven

Build Tools: Maven

Real Java projects are almost never a single file compiled by hand. They pull in dozens of third-party libraries, need to be compiled, tested, packaged, and deployed in a repeatable way, and are usually built by more than one developer on more than one machine. A build tool automates all of that: it downloads the exact dependencies your project needs, compiles your source code in the right order, runs your tests, and produces a distributable artifact — typically a JAR or WAR file — with a single command.
Apache Maven is the build tool that popularized this approach for Java. It is built around three ideas: a declarative project description file (pom.xml), a standard directory layout so tools and IDEs can find your code without configuration, and a fixed build lifecycle made of well-known phases that run in a predictable order.
The Project Object Model (pom.xml)

Every Maven project has a pom.xml file at its root. It declares the project's coordinates (group, artifact, version), the dependencies it needs, and any plugins that customize the build. Maven reads this file, resolves every dependency (and their own transitive dependencies) from a remote repository such as Maven Central, and caches them locally so subsequent builds are fast.

A minimal pom.xml with one dependency

XML
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo-app</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.10.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

The groupId, artifactId, and version together uniquely identify your project (and every dependency you pull in) inside a Maven repository — this triplet is often called the "GAV coordinates."

The Standard Directory Layout

Maven follows a convention-over-configuration philosophy: as long as your files live in the expected places, you don't need to tell Maven where anything is.

Standard Maven project structure

Text
demo-app/
├── pom.xml
└── src/
    ├── main/
    │   ├── java/          (your application source code)
    │   └── resources/     (config files, properties, static assets)
    └── test/
        ├── java/          (unit and integration test source code)
        └── resources/     (test-only config files)
The Build Lifecycle
Maven builds are organized into a sequence of phases. Running a phase runs every phase before it as well, so running package also runs compile and test first.

Phase

What It Does

compile

Compiles the source code in src/main/java

test

Runs unit tests using a testing framework such as JUnit

package

Packages compiled code into a distributable format, e.g. a JAR

install

Installs the package into the local repository (~/.m2), for use as a dependency in other local projects

Running a build from the command line

Text
# remove previous build output, then compile, test, and package/install
mvn clean install

This single command deletes the previous target/ directory, recompiles everything, runs the full test suite, packages the result into a JAR, and installs that JAR into your local repository so other local projects can depend on it.

Warning
If any test fails, the build stops at the test phase by default and never reaches package or install. This is intentional — Maven's philosophy is that a build with failing tests should not produce a distributable artifact.
Note
Dependencies you declare aren't the only ones Maven downloads. If a library you depend on itself depends on other libraries (transitive dependencies), Maven resolves and downloads those too, which is why a fresh project can pull in far more JARs than you explicitly listed.
Tip
Add the -DskipTests flag (mvn install -DskipTests) to skip running tests temporarily, for example while iterating on a packaging problem — but don't make it a habit for real commits.