Article
The Project Object Model (POM) is the fundamental unit of work in Maven. It's an XML file (pom.xml) that contains information about the project and configuration details used by Maven to build the project. This comprehensive guide covers all aspects of POM configuration for Java projects.
Basic POM Structure
Minimal POM Configuration
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <!-- Project Coordinates --> <groupId>com.example</groupId> <artifactId>my-project</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <!-- Project Information --> <name>My Project</name> <description>A sample Maven project</description> <url>http://www.example.com</url> <!-- Properties --> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <!-- Dependencies --> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies> </project>
Project Coordinates and Identification
Complete Project Information
<project> <!-- Basic Coordinates --> <groupId>com.company.product</groupId> <artifactId>user-service</artifactId> <version>2.1.0</version> <packaging>jar</packaging> <!-- jar, war, ear, pom --> <!-- Detailed Information --> <name>User Service API</name> <description>Microservice for user management and authentication</description> <url>https://github.com/company/user-service</url> <!-- Organization --> <organization> <name>Company Inc.</name> <url>https://www.company.com</url> </organization> <!-- Licensing --> <licenses> <license> <name>Apache License, Version 2.0</name> <url>https://www.apache.org/licenses/LICENSE-2.0.txt</url> <distribution>repo</distribution> </license> </licenses> <!-- Developers --> <developers> <developer> <id>johndoe</id> <name>John Doe</name> <email>[email protected]</email> <organization>Company Inc.</organization> <roles> <role>Architect</role> <role>Developer</role> </roles> </developer> </developers> <!-- SCM --> <scm> <connection>scm:git:https://github.com/company/user-service.git</connection> <developerConnection>scm:git:https://github.com/company/user-service.git</developerConnection> <url>https://github.com/company/user-service</url> <tag>v2.1.0</tag> </scm> <!-- Issue Management --> <issueManagement> <system>GitHub</system> <url>https://github.com/company/user-service/issues</url> </issueManagement> </project>
Properties Configuration
Common Properties Setup
<properties>
<!-- Java Version -->
<java.version>11</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<!-- Encoding -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Dependency Versions -->
<spring-boot.version>2.7.0</spring-boot.version>
<junit.version>5.8.2</junit.version>
<logback.version>1.2.11</logback.version>
<jackson.version>2.13.3</jackson.version>
<!-- Plugin Versions -->
<maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefile-plugin.version>
<!-- Custom Properties -->
<docker.image.prefix>company</docker.image.prefix>
<main.class>com.company.Application</main.class>
</properties>
Dependencies Management
Comprehensive Dependencies Section
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Starter Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Database -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.214</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.6</version>
<scope>runtime</scope>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.17.2</version>
<scope>test</scope>
</dependency>
<!-- Logging -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- Optional Dependency -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.1</version>
<optional>true</optional>
</dependency>
</dependencies>
Dependency Scopes Explained
<dependencies>
<!-- compile: Default scope, available in all classpaths -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.20</version>
<scope>compile</scope>
</dependency>
<!-- provided: Like compile, but expected to be provided by JDK or container -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- runtime: Not required for compilation, but for execution -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
<scope>runtime</scope>
</dependency>
<!-- test: Only available for test compilation and execution -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<!-- system: Similar to provided, but system path must be specified -->
<dependency>
<groupId>com.example</groupId>
<artifactId>custom-lib</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/custom-lib.jar</systemPath>
</dependency>
<!-- import: Only used in dependencyManagement in pom type -->
<dependency>
<groupId>com.example</groupId>
<artifactId>bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
Build Configuration
Comprehensive Build Section
<build>
<!-- Default goal when none specified -->
<defaultGoal>compile</defaultGoal>
<!-- Final name of artifact -->
<finalName>${project.artifactId}-${project.version}</finalName>
<!-- Directory structure (optional to override defaults) -->
<directory>${project.basedir}/target</directory>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
<!-- Resources -->
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<excludes>
<exclude>**/test.properties</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/config</directory>
<targetPath>config</targetPath>
</resource>
</resources>
<!-- Test Resources -->
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>
<!-- Plugin Management -->
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<!-- Plugins -->
<plugins>
<!-- Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>11</source>
<target>11</target>
<parameters>true</parameters>
<compilerArgs>
<arg>-Xlint:unchecked</arg>
<arg>-Xlint:deprecation</arg>
</compilerArgs>
</configuration>
</plugin>
<!-- Surefire Plugin for Testing -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
<excludes>
<exclude>**/*IntegrationTest.java</exclude>
</excludes>
<systemPropertyVariables>
<java.util.logging.config.file>src/test/resources/logging.properties</java.util.logging.config.file>
</systemPropertyVariables>
</configuration>
</plugin>
<!-- Failsafe Plugin for Integration Tests -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Jar Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>${main.class}</mainClass>
</manifest>
<manifestEntries>
<Built-By>Maven</Built-By>
<Build-Jdk>${java.version}</Build-Jdk>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!-- Source Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Javadoc Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<source>8</source>
<show>private</show>
<nohelp>true</nohelp>
</configuration>
</plugin>
</plugins>
<!-- Extensions -->
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>3.4.3</version>
</extension>
</extensions>
</build>
Profiles Configuration
Environment-Specific Profiles
<profiles>
<!-- Development Profile -->
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<env>dev</env>
<database.url>jdbc:h2:mem:testdb</database.url>
<log.level>DEBUG</log.level>
</properties>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.214</version>
</dependency>
</dependencies>
</profile>
<!-- Production Profile -->
<profile>
<id>prod</id>
<activation>
<property>
<name>env</name>
<value>prod</value>
</property>
</activation>
<properties>
<env>prod</env>
<database.url>jdbc:postgresql://prod-db:5432/app</database.url>
<log.level>WARN</log.level>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-O</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!-- Integration Test Profile -->
<profile>
<id>integration-test</id>
<activation>
<property>
<name>runITs</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<!-- Docker Build Profile -->
<profile>
<id>docker</id>
<build>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.13</version>
<configuration>
<repository>${docker.image.prefix}/${project.artifactId}</repository>
<tag>${project.version}</tag>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
<executions>
<execution>
<id>default</id>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Multi-Module Project Configuration
Parent POM
<?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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.company</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>Parent Project</name>
<modules>
<module>core</module>
<module>web</module>
<module>api</module>
</modules>
<properties>
<java.version>11</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>2.7.0</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Project Dependencies -->
<dependency>
<groupId>com.company</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Child Module POM
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.company</groupId> <artifactId>parent-project</artifactId> <version>1.0.0</version> <relativePath>../pom.xml</relativePath> </parent> <artifactId>core</artifactId> <packaging>jar</packaging> <dependencies> <!-- No version needed - managed in parent --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- Internal dependency --> <dependency> <groupId>com.company</groupId> <artifactId>common</artifactId> </dependency> </dependencies> </project>
Advanced Configuration Examples
Spring Boot Application
<project> <!-- Spring Boot Parent (optional but recommended) --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.0</version> <relativePath/> </parent> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> <mainClass>com.example.Application</mainClass> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
Docker Build Configuration
<build>
<plugins>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.2.1</version>
<configuration>
<from>
<image>eclipse-temurin:11-jre</image>
</from>
<to>
<image>my-registry/my-app:${project.version}</image>
</to>
<container>
<ports>
<port>8080</port>
</ports>
<environment>
<SPRING_PROFILES_ACTIVE>prod</SPRING_PROFILES_ACTIVE>
</environment>
</container>
</configuration>
</plugin>
</plugins>
</build>
Best Practices
- Use Properties for Versions: Centralize dependency and plugin versions
- Leverage Dependency Management: Use BOMs for consistent versions
- Configure Compiler Plugin: Explicitly set source/target compatibility
- Use Profiles for Environments: Separate configuration for dev/test/prod
- Include Essential Plugins: Source, Javadoc, and Surefire plugins
- Define Resource Filtering: For environment-specific configuration
- Use Final Name: Consistent artifact naming
- Configure Encoding: Prevent platform-specific issues
Conclusion
A well-configured POM file is crucial for maintainable and reproducible builds. By understanding and properly utilizing Maven's POM configuration options, you can create robust build processes that handle dependencies, profiles, multi-module projects, and various build scenarios effectively. The key is to balance flexibility with consistency, ensuring your build configuration scales with your project's complexity while remaining understandable and maintainable.