What is the POM?
The Project Object Model (pom.xml) is the single
file that fully describes a Maven project: its identity, what it depends on,
how it's built, and how it relates to other projects. Every Maven project has
exactly one. This page is the structural reference — for the commands and
day-to-day workflow, see What is Maven?; for the
Super POM and inheritance defaults, that's covered there too.
Full POM Anatomy
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion> <!-- always 4.0.0 — the POM schema version -->
<!-- COORDINATES: groupId:artifactId:version uniquely identifies this artifact -->
<groupId>com.company</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging> <!-- jar | war | pom | ear — see table below -->
<!-- METADATA: descriptive, not functional — useful for generated docs/reports -->
<name>My Application</name>
<description>...</description>
<url>https://github.com/company/my-app</url>
<!-- PARENT: inherit config from another POM (see maven.html for full chain) -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<properties>...</properties> <!-- variables -->
<dependencies>...</dependencies> <!-- libraries this project uses -->
<dependencyManagement>...</dependencyManagement> <!-- version pins, no actual dependency added -->
<build>...</build> <!-- plugins, source dirs, packaging config -->
<profiles>...</profiles> <!-- environment-conditional overrides -->
<modules>...</modules> <!-- only on a multi-module parent -->
</project>
Packaging types
| Value | Produces | Use case |
|---|---|---|
jar |
Standalone Java library/app (default) | Most projects, including Spring Boot apps |
war |
Web application archive | Deploying to an external servlet container (Tomcat) |
pom |
No code — metadata only | Multi-module parent, or a BOM |
ear |
Enterprise archive (bundles multiple modules) | Legacy Jakarta EE application servers |
The build Section
Controls how the project compiles and packages. Most of it can be omitted — inherited from the Super POM or your parent — and only needs overriding when you need non-default behaviour.
<build>
<finalName>my-app</finalName> <!-- overrides the default artifactId-version naming -->
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering> <!-- substitutes ${property} placeholders in resource files -->
</resource>
</resources>
<plugins>
<!-- Surefire: runs unit tests during the 'test' phase -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<excludes><exclude>**/*IntegrationTest.java</exclude></excludes>
</configuration>
</plugin>
<!-- Failsafe: runs INTEGRATION tests during 'verify' — separate from Surefire
on purpose, so 'mvn test' stays fast and 'mvn verify' covers integration -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals><goal>integration-test</goal><goal>verify</goal></goals>
</execution>
</executions>
</plugin>
<!-- Jar plugin: sets the entry point for an executable jar
(Spring Boot apps use spring-boot-maven-plugin instead — it bundles
dependencies into a fat jar, which the plain jar plugin doesn't) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive><manifest><mainClass>com.company.Main</mainClass></manifest></archive>
</configuration>
</plugin>
</plugins>
</build>
Notice none of the plugins above declare a <version>.
When using spring-boot-starter-parent (or any well-built
parent), plugin versions are already pinned in its
pluginManagement — declaring your own version risks
drifting out of sync with what the rest of the Spring Boot ecosystem
expects.
Multi-Module Projects
A multi-module (or "reactor") project splits a codebase into independently
buildable modules sharing one parent. Common real-world split: a
core domain module, an api module, a
web module — each its own deployable artifact, all versioned
and configured together.
// Parent pom.xml — lives at the project root, packaging MUST be 'pom'
<project>
<groupId>com.company</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>core</module> <!-- each maps to a subdirectory with its own pom.xml -->
<module>api</module>
<module>web</module>
</modules>
<!-- Pin versions ONCE here — every module inherits these, no repetition -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.company</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version> <!-- so api/web can depend on core w/o repeating version -->
</dependency>
</dependencies>
</dependencyManagement>
</project>
// api/pom.xml — child module, depends on the sibling 'core' module
<project>
<parent>
<groupId>com.company</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>api</artifactId> <!-- groupId, version inherited — not repeated -->
<dependencies>
<dependency>
<groupId>com.company</groupId>
<artifactId>core</artifactId> <!-- no version needed — pinned by parent's dependencyManagement -->
</dependency>
</dependencies>
</project>
# Building from the parent root builds ALL modules, in dependency order
# (Maven computes the order automatically — core before api, api before web)
mvn clean install
# Build only one module and its dependencies — much faster during dev
mvn install -pl api -am
# ^pl = "project list" ^am = "also make" (build dependencies too)
# Build everything EXCEPT one module
mvn install -pl '!web'
# Resume a failed multi-module build from where it broke —
# saves rebuilding the modules that already succeeded
mvn install -rf :api
A parent POM (like spring-boot-starter-parent)
is purely about configuration inheritance — it might not even be in the
same repository. A multi-module parent additionally
declares <modules> and physically aggregates sibling
directories for a single coordinated build. A project can use both
simultaneously: a multi-module parent for the reactor build, while each
child module ALSO inherits from spring-boot-starter-parent
via its own <parent> — though typically only the
immediate parent chain is followed, so this requires deliberate design
(often via importing a BOM instead of double parenting).
Interview Questions
Q: What are GAV coordinates?
groupId:artifactId:version — the three values that together
uniquely identify any artifact in a Maven repository. groupId
is typically a reversed domain (the organisation); artifactId
is the project name; version distinguishes releases.
Q: What is the difference between dependencies and dependencyManagement?
<dependencies> actually adds a library to the project's
classpath. <dependencyManagement> only pins a version for
if/when that dependency is declared (here or in a child module) — it adds
nothing by itself. This is what lets a parent POM centralise version control
without forcing every module to use every dependency.
Q: How does Maven determine build order in a multi-module reactor?
Maven analyses the inter-module dependencies declared in each child's
<dependencies> and computes a directed graph, then builds
in topological order — a module is never built before its dependencies. The
order in <modules> is just a hint; Maven will reorder if
the declared list doesn't match the actual dependency graph.
Q: When would you use Failsafe instead of Surefire for tests?
Surefire runs during the test phase and is meant for fast unit
tests with no external dependencies — it should never fail the
package phase silently skipping deployment prep. Failsafe runs
during integration-test/verify, specifically
designed so failures there don't prevent the artifact from being packaged
(only from being installed/deployed) — useful when integration tests need a
packaged artifact to already exist (e.g. a running Docker container built
from it) before they can run.