Maven ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-11-06

Maven pom.xml Explained: Structure, GAV, Properties, Packaging and Build

pom.xml is the XML file in the root of a Maven project that holds the POM (Project Object Model): the project coordinates (groupId, artifactId, version), the build type (packaging), properties, dependencies and build settings. Maven reads pom.xml on every run, validates its structure and merges it with the defaults; it then uses the resulting model to run the requested phases and goals: resolving dependencies, compiling the code, packaging the result and so on.

Older Maven tutorials often contain this snippet: <sourceDirectory>src</sourceDirectory>. Paste it into a project with the standard layout and Maven starts compiling the Java sources in the whole src tree as main code, including the tests in src/test/java. If the project has JUnit Jupiter tests with test scope, mvn compile then fails with package org.junit.jupiter.api does not exist: JUnit is on the test classpath only, so the main code cannot see it. One line in pom.xml breaks the build. Below we go through which elements of this file you actually need and what each of them changes.

pom.xml Structure: the Main Elements

A Maven POM file needs only the <project> root element, <modelVersion> and the coordinates groupId, artifactId and version (groupId and version can be inherited from a parent). Everything else is optional: settings you leave out may be inherited from a parent and the Super POM or come from the POM model or the plugins themselves, and an optional element may simply be absent. The default bindings of plugin goals to phases are determined by packaging.

Element What it defines Required? If omitted
modelVersion The POM format version Yes The POM cannot be read
groupId, artifactId, version The project coordinates (GAV) Yes (groupId and version only if there is no parent) An error such as 'groupId' is missing
packaging The artifact type and the set of plugins bound to build phases No jar
name, description, url Human-readable project information and the project site No The build header shows the artifactId instead of name; description and url can be inherited from a parent
properties Values for ${...} substitution and plugin settings No Plugins use inherited values or their own defaults (in Maven 3.9.x, for example, the platform encoding)
dependencies Libraries the project needs No No dependencies declared directly in the project (they can still come from a parent or a profile)
build Directories, resources, the output file name, plugins No Standard directories from the Super POM; plugin goal bindings from the lifecycle mapping for the packaging
parent The parent POM whose settings are inherited No The Super POM acts as the parent

The order of elements inside <project> does not affect the result, but by convention they follow the order of the table: coordinates first, then description, properties, dependencies and build. The shortest working 12-line pom.xml is shown in the lesson What Is Maven; below, each element is explained separately, and at the end everything is put together in a full example.

The project Root Element and modelVersion

The <project> root element contains the entire project description, and the nested <modelVersion> with the value 4.0.0 tells Maven which version of the POM model the file follows. For Maven 3 this is the only valid value. The namespace attributes and xsi:schemaLocation are optional, but they let your IDE validate tags and autocomplete them.

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- coordinates, properties, dependencies, build -->
</project>

The xmlns value is a namespace identifier, not a download address, so there is no need to change http to https in it. Maven 4 introduces model version 4.1.0 with new features, but it still reads files with 4.0.0, so for learning and most real-world projects stick with 4.0.0.

GAV Coordinates: groupId, artifactId, version

GAV is the triple of groupId, artifactId and version that uniquely identifies a project (or any library) in the Maven world. groupId is the organization or project group written as a reversed domain name, artifactId is the project name, and version is its version. The pair groupId + artifactId without a version identifies the project as a whole, not a specific build. A specific file is further distinguished by its extension (type) and, when needed, a classifier: the main jar and the sources jar (-sources) share the same GAV.

<groupId>com.examclouds</groupId>
<artifactId>shop</artifactId>
<version>1.0-SNAPSHOT</version>

The coordinates determine both the path in the repository and the file name: after mvn install, the project above ends up in ~/.m2/repository/com/examclouds/shop/1.0-SNAPSHOT/shop-1.0-SNAPSHOT.jar. How to choose the values (which characters are allowed, why it is recommended to avoid com.my-company as a groupId) is covered in How to Choose groupId, artifactId and version; the official recommendations are in the Guide to Naming Conventions.

SNAPSHOT vs release. The -SNAPSHOT suffix (1.0-SNAPSHOT) marks a version under development: its content may change. Maven looks for a newer build of a SNAPSHOT dependency in the remote repository during a build, when the repository's updatePolicy says it is due (the default is daily — at most once a day); you can force an update of SNAPSHOT dependencies with mvn -U. Maven Central itself is configured in the Super POM with snapshots disabled, so SNAPSHOT versions come from a separate (usually corporate) repository. A version without the suffix (1.0.0) is a release: a published release is treated as immutable, and on Maven Central it can be neither overwritten nor deleted. That is why a fix to a release is shipped as a new version (1.0.1).

Maven Packaging: jar, war, pom

The <packaging> element defines what artifact the build produces and therefore the default bindings of plugin goals to lifecycle phases (the lifecycle mapping, see Plugin Bindings for default Lifecycle). The element is optional: if it is missing, jar is used — the default value of the POM model itself.

packaging Build result What runs in the package phase When to use it
jar (default) target/artifactId-version.jar maven-jar-plugin Console applications, libraries, Spring Boot applications
war A .war web archive with the content of src/main/webapp maven-war-plugin A web application deployed to Tomcat or another servlet container
pom Only the pom.xml itself, no code and no jar Nothing: no compilation or packaging Parent POMs, aggregators of a multi-module project (the <modules> list), BOMs and other projects that publish only a POM
maven-plugin, ear, ejb A Maven plugin, a Jakarta EE enterprise archive, an EJB module The corresponding plugins Special cases

Because of this binding, changing packaging changes the whole build: set it to pom in a project with code in src/main/java, and mvn package ends with BUILD SUCCESS without compiling a single class. The build phases and their order are covered in a separate lesson of this module on the Maven build lifecycle.

name, description and url

The <name>, <description> and <url> elements describe the project for people: a readable name, a short description and the address of the project site or repository. On their own they do not define any build steps, but plugins may use this metadata (for example, when generating the project site). name appears in Maven's output, while description and url can be inherited from a parent. All three are required when you publish a library to Maven Central.

<name>Online Shop</name>
<description>A training online shop in Java</description>
<url>https://github.com/examclouds/shop</url>

When name is set, Maven shows it in the build header instead of the artifactId (without name the header reads Building shop 1.0-SNAPSHOT):

[INFO] -----------------------< com.examclouds:shop >------------------------
[INFO] Building Online Shop 1.0-SNAPSHOT
[INFO]   from pom.xml

Open-source libraries usually add <licenses>, <developers> and <scm> (the source code repository) next to them — more metadata that Maven Central requires.

Maven Properties and ${...} Expressions

The <properties> section stores name-value pairs that can be substituted with the expression ${name} into many POM elements, such as dependency versions and plugin configuration. Some fields have restrictions: in particular, the <parent> coordinates must be literal values in Maven 3. Maven properties serve two purposes: they remove duplication (for example, one version shared by several related libraries) and they pass parameters to plugins — the Java version and the source encoding.

<properties>
    <!-- read by plugins -->
    <maven.compiler.release>25</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- a custom property -->
    <junit.version>5.11.4</junit.version>
</properties>

<!-- usage: -->
<version>${junit.version}</version>
  • maven.compiler.release — the Java version for maven-compiler-plugin: language level, bytecode and JDK API. Why you need release rather than the source/target pair is shown in the lesson What Is Maven.
  • project.build.sourceEncoding — the encoding of sources and resources for the compiler and resources plugins. Setting UTF-8 explicitly keeps the build consistent across Maven versions. In Maven 3.9.x, with no encoding set or inherited, plugins may fall back to the platform encoding and print a warning (for filtered resources, depending on the plugin version, Using platform encoding (...) to copy filtered resources, i.e. build is platform dependent!), and on JDK 17 and earlier on Windows non-ASCII characters in string literals can turn into garbage. Since Maven 3.10.0, UTF-8 is already set in the Super POM.
  • Custom properties (junit.version) can have any name that is a valid XML element name; dots are simply part of the name, not nesting.

Besides your own properties, expressions can use built-in project model values, environment variables and Java system properties:

Expression What it resolves to Example value
${project.version} The project version from <version> 1.0-SNAPSHOT
${project.groupId}, ${project.artifactId} The project coordinates com.examclouds, shop
${project.basedir} The directory containing pom.xml /home/user/shop
${project.build.directory} The build output directory /home/user/shop/target
${project.build.finalName} The output file name without extension shop-1.0-SNAPSHOT
${env.JAVA_HOME} An environment variable (env. prefix) /usr/lib/jvm/java-25-openjdk
${java.version}, ${user.home} System properties of the JVM running Maven 25.0.1, /home/user

A property can be overridden from the command line without editing the file: mvn package -Djunit.version=5.12.0 — a value passed with -D takes precedence over <properties>. The short forms ${pom.version} and ${version} found in old articles are deprecated: Maven 3 prints The expression ${pom.version} is deprecated. Please use ${project.version} instead., so always write the full project. prefix.

Quirk: a typo in ${...} does not always break the build

An unresolved expression does not always cause an immediate error. In a dependency version (<version>${junit.verison}</version>) it leads to a model validation error: Maven 3 rejects the string as an invalid version. In a filtered text resource the expression may simply stay unreplaced as ${junit.verison}. In plugin configuration the outcome depends on the parameter and the plugin itself: the expression may be kept, evaluated later, become an empty value or cause a plugin error. To see what the expressions in the model resolved to, check the effective POM.

The dependencies Section

The <dependencies> section lists the libraries your project needs; each one is declared with a <dependency> element using the same GAV coordinates as the project itself. Maven resolves the listed dependencies (finds them in the local repository or downloads them from a remote one) and adds them to the classpath. The optional <scope> element specifies where the library is needed: test means "only for compiling and running tests".

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

The other scopes, transitive dependencies, version conflict resolution, exclusions and centralized version management will be covered in a separate lesson of this module on Maven dependencies.

The build Section: finalName, Directories, Resources, Plugins

The <build> section describes how the project is built: the output file name, the source and resource directories and the plugins in use. It is optional: for a project with the standard layout, all paths are already set in the Super POM, so you need <build> only to change the defaults or configure plugins.

Default directories and when to override them

Element Default value What it defines
sourceDirectory src/main/java Main source code (a single directory only)
testSourceDirectory src/test/java Test sources
resources / resource src/main/resources Resource directories copied to target/classes
directory target The directory for all build output
outputDirectory target/classes Where .class files and resources go
finalName ${project.artifactId}-${project.version} The name of the output jar/war in target (without extension)

Override directories only when you move an old project to Maven and cannot change its layout. In that case set both paths explicitly so that main code and tests do not overlap (otherwise you get the error from the beginning of this lesson):

<build>
    <sourceDirectory>src</sourceDirectory>
    <testSourceDirectory>test</testSourceDirectory>
</build>

For a new project, it is better to put files into the standard directories — see the table with examples in Maven Project Structure.

finalName: the name in target vs the name in the repository

<finalName> changes only the file name in the target directory. When installing into the local repository, Maven still names the file by its coordinates, artifactId-version, because other projects find it by GAV:

<build>
    <finalName>shop</finalName>
</build>
mvn install
# Output (fragment):
[INFO] Building jar: /home/user/shop/target/shop.jar
[INFO] Installing /home/user/shop/target/shop.jar to /home/user/.m2/repository/com/examclouds/shop/1.0-SNAPSHOT/shop-1.0-SNAPSHOT.jar

finalName is most often set for a war: Tomcat derives the context path from the file name, so shop.war is served at /shop instead of /shop-1.0-SNAPSHOT.

Resources and filtering

Resources (.properties, .xml, templates) are not compiled; they are copied to target/classes and end up on the classpath. If you enable <filtering>true</filtering> for a directory, Maven replaces ${...} expressions in the files with property values while copying — one way to pass the project version to your program:

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
# src/main/resources/app.properties
app.version=${project.version}

# target/classes/app.properties after mvn package
app.version=1.0-SNAPSHOT

Not obvious: resources replaces, it does not add

The <resources> list in your pom.xml completely replaces the list from the Super POM. If you declare only <directory>src/main/config</directory>, src/main/resources is no longer copied: the build succeeds, but getClass().getResourceAsStream("/app.properties") returns null. When adding your own resource directory, list the standard one too.

plugins vs pluginManagement

All the work during a build is done by plugins. <build><plugins> lists the plugins to run or configure in this project, while <build><pluginManagement> only sets versions and configuration "in case they are used": on its own it activates nothing and is most often found in a parent POM. The standard plugins (compiler, surefire, jar) work without being declared, but you should pin their versions, otherwise they depend on your Maven version. How to declare plugins, bind their goals to phases and configure them is covered in a separate lesson of this module on Maven plugins.

The reporting section

The <reporting> section adds report plugins (Javadoc, test results, static analysis) that run only in the site lifecycle, when the project site is generated: with mvn site or other phases of that lifecycle (for example, mvn site-deploy). It takes no part in a regular build (mvn package), and modern projects usually run quality checks in <build> or on CI. The FindBugs plugin from old examples is no longer developed — it has been replaced by spotbugs-maven-plugin.

parent, Super POM and Effective POM

Every pom.xml inherits settings from a parent: either an explicit one declared in <parent> or the implicit Super POM built into Maven. The final model Maven gets after inheritance, profile activation and property interpolation is called the effective POM; the command mvn help:effective-pom prints it.

parent. A parent POM has packaging set to pom; a child project references it by GAV and inherits properties, dependencies, pluginManagement, groupId and version. artifactId is not inherited — every module has its own. This is how multi-module projects and Spring Boot (with the spring-boot-starter-parent parent) work.

<parent>
    <groupId>com.examclouds</groupId>
    <artifactId>shop-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!-- by default the parent is looked up in ../pom.xml -->
</parent>
<artifactId>shop-core</artifactId>
<!-- groupId and version are inherited -->

The Super POM defines defaults for every project: the standard directories src/main/java and target, resources, finalName and the Maven Central repository. That is why a minimal pom.xml of just a few lines can already build a project. But not every default comes from the Super POM: packaging = jar is a default of the POM model itself, the bindings of compiler, surefire and jar to phases come from the lifecycle mapping for the chosen packaging, and plugins have their own default parameter values.

The effective POM is your main tool when you cannot tell where a setting comes from or why a property was not applied:

mvn help:effective-pom
# save to a file:
mvn help:effective-pom -Doutput=effective-pom.xml
<!-- Output (fragment): paths are absolute, properties are resolved -->
<build>
  <sourceDirectory>/home/user/shop/src/main/java</sourceDirectory>
  <testSourceDirectory>/home/user/shop/src/test/java</testSourceDirectory>
  <outputDirectory>/home/user/shop/target/classes</outputDirectory>
  <directory>/home/user/shop/target</directory>
  <finalName>shop-1.0-SNAPSHOT</finalName>
  ...

The effective POM shows everything that is not in your file: default plugin versions, the Maven Central repository, dependencies inherited from the parent. Keep its limits in mind: it is the final model, not every actual plugin execution parameter (defaults defined inside a plugin are not shown) and not the contents of filtered files. Declaring your own repositories in <repositories> is covered in a separate lesson of this module on Maven repositories.

Full pom.xml Example for Java 25

Below is a complete pom.xml for a Java 25 console application: coordinates, description, properties with the Java version and encoding, a JUnit 5 test dependency, the output file name, resource filtering and pinned versions of the standard plugins. For Java 21, replace 25 with 21.

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- project coordinates (GAV) -->
    <groupId>com.examclouds</groupId>
    <artifactId>shop</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <!-- human-readable description -->
    <name>Online Shop</name>
    <description>A training online shop in Java</description>

    <properties>
        <maven.compiler.release>25</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit.version>5.11.4</junit.version>
    </properties>

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

    <build>
        <finalName>shop</finalName>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <pluginManagement>
            <!-- plugin versions: check the latest version on Maven Central -->
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.13.0</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>3.5.2</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>
mvn package
# Output (fragment, the project has one test):
[INFO] Building Online Shop 1.0-SNAPSHOT
...
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] Building jar: /home/user/shop/target/shop.jar
[INFO] BUILD SUCCESS

If the project has no tests, you will see No tests to run. instead of the Tests run line. If the compiler used for the build (the JDK running Maven or a JDK selected via toolchains) does not support --release 25, the build stops at compilation with an error like release version 25 not supported; the exact text depends on the JDK and the maven-compiler-plugin version.

Common pom.xml Mistakes

Most pom.xml problems are not XML errors: the file is valid and the build often even succeeds, but the result is not what you expected. The table shows what you will see in the output or at runtime and how to fix it.

What was written What happens How to fix it
<sourceDirectory>src</sourceDirectory> with the standard layout Java sources in the whole src tree, tests included, are compiled as main code; with JUnit Jupiter tests in test scope you get package org.junit.jupiter.api does not exist Remove the line — the default src/main/java is already correct
A plugin in <plugins> without <version>, when no version comes from a parent or pluginManagement (including the Super POM) Warning 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-compiler-plugin is missing; the version depends on the installed Maven Set the version in <plugin> or in pluginManagement; if a parent or pluginManagement already provides it, there is no need to repeat it locally
Your own <resources> without src/main/resources Files from src/main/resources are missing from the jar; getResourceAsStream returns null List the standard directory in <resources> as well
<filtering>true</filtering> on a directory with fonts and other binary files Binary files may get corrupted while copying (filtering reads them as text). The plugin skips jpg, jpeg, gif, bmp and png by default, but fonts and other formats are still filtered Filter a separate directory or only specific files via <includes>, and copy the rest with a second <resource> block without filtering — otherwise they are left out of the build; or add the extensions to nonFilteredFileExtensions (documentation)
${pom.version} or ${version} Warning The expression ${pom.version} is deprecated Use ${project.version}
<packaging>pom</packaging> in a project with code BUILD SUCCESS, but no classes are compiled and no jar is created Remove packaging (it becomes jar) — pom is for parent POMs, aggregators, BOMs and other projects that publish only a POM
Expecting finalName to change the name in ~/.m2 In the local repository the file is still named artifactId-version.jar This is expected: dependencies are resolved by GAV, not by file name

Frequently Asked Questions

Can the POM file have a name other than pom.xml?

Yes, but then you have to pass it on every run: mvn -f build-pom.xml package (the long form is --file). Without this flag Maven looks for pom.xml in the current directory, and IDEs and CI servers expect the standard name too, so do not rename the file without a good reason.

What is the difference between pom.xml and settings.xml?

pom.xml describes a specific project and lives in its Git repository. settings.xml holds Maven settings for a particular machine: the user file ~/.m2/settings.xml and the global conf/settings.xml in the Maven installation directory. They define the local repository path, mirrors, proxies, and logins and passwords for corporate repositories. That is why passwords never go into pom.xml. Repository types are covered in a separate lesson of this module.

How do I read the project version from pom.xml in Java code?

A convenient way is resource filtering: the line app.version=${project.version} in src/main/resources/app.properties becomes app.version=1.0-SNAPSHOT during the build, and your code reads the file with Properties and getResourceAsStream. The setup is shown in The build Section. It is not the only way and not always reliable in an IDE: if the build is not delegated to Maven, filtering may not run. By default maven-jar-plugin also puts META-INF/maven/{groupId}/{artifactId}/pom.properties with the version into the jar, but this file is absent when you run from the IDE (from target/classes).

Is artifactId inherited from the parent POM?

No. A child inherits groupId, version, properties, dependencies and plugin configuration from the parent, but every module must declare its own artifactId — otherwise two modules would have identical coordinates. groupId and version are usually omitted in a child module when they match the parent. More details in parent, Super POM and Effective POM.

Comments

Please log in or register to have a possibility to add comment.