POM.xml Structure

Full anatomy of the Project Object Model — and how multi-module projects share configuration

← Back to Index

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>
Plugin versions: let the parent decide

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
Don't confuse parent POM with multi-module parent

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

🎓 Junior level

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.

🔥 Senior level

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.