W3docs

Java Maven pom.xml

The Maven Project Object Model — pom.xml structure, coordinates, properties, and inheritance.

Java Maven pom.xml

The pom.xml is the heart of any Maven project. POM stands for Project Object Model — a single XML file that declares what your project is (its identity), what it needs (its dependencies), and how to build it (plugins and configuration). Maven reads this file, downloads everything it references from a repository, and runs the build. Where an ad-hoc project scatters this knowledge across shell scripts and a lib/ folder of hand-copied JARs, Maven puts it all in one declarative, version-controlled document.

The minimal POM

Every POM is an XML document rooted in a <project> element with the Maven 4.0.0 schema. The smallest useful POM declares its model version and its coordinates — the four values that name the artifact:

<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>

  <groupId>com.w3docs</groupId>
  <artifactId>shop-api</artifactId>
  <version>1.4.0</version>
  <packaging>jar</packaging>
</project>

<modelVersion> is always 4.0.0 — it is the version of the POM format, not of your code. Leave it exactly as shown.

Coordinates: groupId, artifactId, version

A Maven artifact is identified by its GAV coordinates. Every dependency you ever add is named by the same three (sometimes four) values, so it is worth learning them precisely:

ElementMeaningConvention
groupIdThe organization or namespaceReverse-DNS, e.g. com.google.code.gson
artifactIdThe project name within the groupLowercase, hyphenated, e.g. shop-api
versionThe release of this artifactSemantic, e.g. 1.4.0; -SNAPSHOT for in-progress builds
packagingThe output typejar (default), war, pom

Together, groupId:artifactId:version is globally unique. It is what Maven uses to locate a JAR in a repository and to name the artifact your build produces. A version ending in -SNAPSHOT (e.g. 1.5.0-SNAPSHOT) is a mutable, in-development build that Maven may re-download; a plain version is treated as immutable.

Declaring dependencies

Dependencies are listed under <dependencies>, each one a <dependency> block giving the coordinates of a library you need. Maven resolves them — and their dependencies, transitively — from a repository (Maven Central by default):

<dependencies>
  <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.11.0</version>
  </dependency>
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
  </dependency>
</dependencies>

The <scope> controls when a dependency is on the classpath. compile (the default) means everywhere; test means only while compiling and running tests, so JUnit never ships inside your JAR. Other scopes include provided (supplied by the runtime, e.g. a servlet API) and runtime (needed to run but not to compile, e.g. a JDBC driver).

Properties: don't repeat versions

The <properties> element defines reusable variables, referenced elsewhere with ${name} syntax. The idiom is to pin every library version once at the top so upgrades happen in a single place:

<properties>
  <maven.compiler.release>21</maven.compiler.release>
  <junit.version>5.10.2</junit.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>${junit.version}</version>
    <scope>test</scope>
  </dependency>
</dependencies>

maven.compiler.release is a well-known property that tells the compiler plugin which Java release to target — cleaner than configuring the plugin by hand. When Maven reads the POM it interpolates every ${...} placeholder, substituting the property's value before resolving anything.

Inheritance with a parent POM

POMs form a hierarchy. A <parent> element makes a project inherit coordinates, properties, and dependency management from another POM. Spring Boot's starter parent is the classic example — it fixes a consistent set of library versions so you can omit <version> on managed dependencies:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.3.2</version>
</parent>

<dependencies>
  <dependency>
    <!-- version omitted: inherited from the parent's dependencyManagement -->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

A multi-module build uses the same mechanism in reverse: one aggregator POM with <packaging>pom</packaging> lists <modules>, and each child names it as <parent>. Shared configuration lives in one place; the children stay tiny.

A worked example: reading a POM the way Maven does

A pom.xml is just XML, so we can parse one with the JDK's built-in DOM API and walk its structure exactly as Maven would — reading the coordinates, the properties, and each dependency, while interpolating a ${...} placeholder by hand. (Maven itself is a build tool, not a library on the classpath here, but this shows the model it operates on.)

java— editable, runs on the server

What to take from the run:

  • The modelVersion printed as 4.0.0 — the constant every POM carries. It identifies the POM format, not your project, and Maven would reject a POM that omitted it.
  • The coordinates came out as com.w3docs:shop-api:1.4.0, the GAV triple in its canonical groupId:artifactId:version form. This single string is how Maven names both the artifact this build produces and every dependency it resolves.
  • packaging read back as jar, the default output type. Had it been pom, this would be an aggregator/parent project that produces no JAR of its own.
  • The junit.version property resolved to 5.10.2, and the JUnit dependency's ${junit.version} placeholder was interpolated to that same value — exactly the substitution Maven performs so a version is declared in one place and reused.
  • The dependency walk reported two dependencies and printed each with its scope: Gson defaulted to compile (no <scope> element, so it is on every classpath), while JUnit showed scope=test, keeping it out of the shipped artifact.

Practice

Practice

In a pom.xml, what is the purpose of the three elements groupId, artifactId, and version together?