Maven: Project Structure and pom.xml

2 minute read

Every Maven project is defined by a single file: pom.xml (Project Object Model). It tells Maven how to build your project, what dependencies it needs, and how to package it.


Standard Directory Layout

my-project/
├── pom.xml                          ← Project definition
├── src/
│   ├── main/
│   │   ├── java/                    ← Application source code
│   │   │   └── com/myapp/
│   │   │       └── App.java
│   │   └── resources/               ← Config files, properties
│   │       └── application.properties
│   └── test/
│       ├── java/                    ← Test source code
│       │   └── com/myapp/
│       │       └── AppTest.java
│       └── resources/               ← Test config files
│           └── test-data.csv
└── target/                          ← Build output (generated)
    ├── classes/
    ├── test-classes/
    └── my-project-1.0.0.jar

Convention over configuration: Maven expects this layout. Don’t fight it — follow it.


Creating a Maven Project

mvn archetype:generate \
  -DgroupId=com.myapp \
  -DartifactId=my-project \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DarchetypeVersion=1.5 \
  -DinteractiveMode=false

Or simply create the directory structure and pom.xml manually.


pom.xml Anatomy

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

    <!-- ═══ GAV Coordinates ═══ -->
    <groupId>com.myapp</groupId>
    <artifactId>my-project</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <name>My Project</name>
    <description>A sample Maven project</description>

    <!-- ═══ Properties ═══ -->
    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit.version>5.10.3</junit.version>
    </properties>

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

    <!-- ═══ Build Configuration ═══ -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <release>21</release>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

GAV Coordinates

Every Maven artifact is uniquely identified by three values:

Element Purpose Example
groupId Organization/domain (reversed) com.myapp
artifactId Project name my-project
version Release version 1.0.0
com.myapp:my-project:1.0.0

This is how Maven finds artifacts in repositories.


Properties

Properties are variables you can reuse throughout the POM:

<properties>
    <java.version>21</java.version>
    <spring-boot.version>3.3.0</spring-boot.version>
    <lombok.version>1.18.34</lombok.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>  <!-- references property -->
    </dependency>
</dependencies>

Built-in Properties

Property Value
${project.version} Current project version
${project.basedir} Project root directory
${project.build.directory} target/
${java.home} JDK installation path

Packaging Types

<packaging>jar</packaging>   <!-- default — executable JAR -->
<packaging>war</packaging>   <!-- web application -->
<packaging>pom</packaging>   <!-- parent/BOM — no code -->

Parent POM and Inheritance

<!-- Parent POM (shared settings) -->
<project>
    <groupId>com.myapp</groupId>
    <artifactId>parent</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>

    <properties>
        <java.version>21</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Versions defined here, children don't need version -->
        </dependencies>
    </dependencyManagement>
</project>

<!-- Child POM -->
<project>
    <parent>
        <groupId>com.myapp</groupId>
        <artifactId>parent</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>user-service</artifactId>
    <!-- inherits groupId, version, properties from parent -->
</project>

The Super POM

Every POM implicitly extends Maven’s Super POM, which defines:

  • Maven Central repository
  • Standard directory layout
  • Default plugin versions

View the merged result:

mvn help:effective-pom

This shows the effective POM — your POM + parent POM + Super POM merged together.


Common Mistakes

Mistake Fix
Non-standard directory structure Stick to src/main/java and src/test/java
Hardcoded versions everywhere Use <properties> for version management
Missing <packaging> Defaults to jar — explicit is better
Editing files in target/ target/ is generated — edit source, rebuild

Key Takeaways

  1. pom.xml is the single source of truth for a Maven project.
  2. GAV coordinates (groupId:artifactId:version) uniquely identify every artifact.
  3. Follow the standard directory layout — Maven expects it.
  4. Use <properties> for version management.
  5. Parent POMs share configuration across multiple modules.

What’s Next?

Next: Maven Lifecycle, Goals, and Phases — understand what happens when you run mvn package.

Updated: