Skip to content
Ameen Abozaid
PublishedBEG-054-L02

Project structure and pom.xml

  • Lesson 2 of 6
  • 10 min read
  • Beginner
On This Lesson 11 topics

Lesson Overview

In the last lesson you saw that a build tool follows a "card" kept inside the project. In Maven that card is a file named pom.xml. This lesson teaches the two things you need to read any Maven project: the folder layout Maven expects, and the parts of the pom.xml.

Maven works by convention over configuration: if you put files where Maven expects them, you hardly need to tell it anything. That is why the wrong folder layout is the most common beginner error, and why we start there.

Why this matters: every Java backend project you open at work will have this shape. Reading it fluently saves hours. This lesson builds on lesson 1 (why build tools) and on the packages and folders you used in earlier Java modules.

Learning Objectives

By the end of this lesson you will be able to:

  • Place main code, test code, and pom.xml in the correct Maven locations.
  • Say what the target/ folder is and why it is not part of your source.
  • Name and explain the three coordinates: groupId, artifactId, version.
  • Read a pom.xml and find its properties, dependencies, and plugins.
  • Explain what <modelVersion> and <packaging> mean.
  • Recognize the error Maven prints when a required coordinate is missing.

Written Explanation

The standard directory layout

Maven expects this shape (this is the layout used throughout the module):

text
hello/
├── pom.xml
└── src/
    ├── main/
    │   └── java/
    │       └── com/example/Greeter.java
    └── test/
        └── java/
            └── com/example/GreeterTest.java

The rules:

PathWhat goes there
pom.xmlThe project description, at the root of the project
src/main/javaYour application code (package folders start here)
src/test/javaYour test code (same packages as the code under test)
src/main/resourcesNon-code files the program needs, such as configuration files (optional)
target/Everything Maven generates: compiled classes, reports, the JAR

Notice that the package com.example becomes the folders com/example under src/main/java. The folder src/main/java itself is not part of the package name. You already did this in earlier modules; Maven just fixes the starting folder.

Test code lives in a separate tree so it is compiled and run for the tests but is not packaged into the final JAR. The test class GreeterTest is in the same package as Greeter, which lets it use package-private members.

The target/ folder

After a build, a target/ folder appears. It holds compiled .class files (target/classes), compiled tests (target/test-classes), test reports (target/surefire-reports), and the JAR. Real listing from a build of the example project:

text
target/classes
target/test-classes
target/surefire-reports
target/hello-1.0-SNAPSHOT.jar

Everything in target/ can be rebuilt from your sources at any time, so it is never edited by hand and never committed to Git. Deleting it is always safe.

The pom.xml

POM stands for Project Object Model. The pom.xml is an XML file that describes your project and tells Maven how to build it. XML is made of nested tags such as <name>value</name>; you only need to read it, not master it.

Here is the complete pom.xml of the example project:

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

  <groupId>com.example</groupId>
  <artifactId>hello</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.release>21</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>5.12.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Read it top to bottom:

  • The opening <project ...> lines are boilerplate: they name the XML format. Copy them; do not worry about them.
  • <modelVersion> is the version of the POM format itself. It is 4.0.0 for all current Maven projects.
  • Coordinates (next section) say who and what this project is.
  • <properties> are named settings. maven.compiler.release sets the Java version used to compile; project.build.sourceEncoding sets the text encoding of source files. Always set both, as explained below.
  • <dependencies> lists the libraries the project uses (lesson 3).

If <packaging> is not written, Maven assumes jar, which is what we want.

Coordinates

Every Maven project is identified by three values:

CoordinateMeaningExample
groupIdThe organization or group that owns it, usually a reversed domain namecom.example
artifactIdThe name of this project (and of its JAR)hello
versionWhich version of the project this is1.0-SNAPSHOT

They are written together as groupId:artifactId:version, here com.example:hello:1.0-SNAPSHOT. The same three values identify every library you depend on, which is how Maven finds them in a repository.

The suffix -SNAPSHOT marks a version still under development. The JAR name follows the pattern artifactId-version.jar, which is why the real file is hello-1.0-SNAPSHOT.jar.

If a coordinate is missing, Maven refuses to start. Removing groupId from a test project gives this real message:

text
[ERROR]   The project [unknown-group-id]:demo:1.0 (...pom.xml) has 1 error
[ERROR]     'groupId' is missing. @ line 1, column 52

Why set the Java version

Without a Java version property, Maven uses defaults that may be much older than your JDK. In a test project with no properties, the real build output was:

text
[INFO] Compiling 1 source file with javac [debug target 1.8] to target\classes
[WARNING] source value 8 is obsolete and will be removed in a future release

That is Java 8 compatibility settings on a JDK 21: modern syntax would fail, and the warnings are noise. The default depends on the Maven compiler plugin version, so do not rely on it: set maven.compiler.release explicitly. (release tells javac which Java version's language and library to compile against.) With the property set to 21, the build compiled with no such warning.

Plugins

Maven itself knows very little. The work (compiling, running tests, creating the JAR) is done by plugins, small programs Maven downloads on demand. You can see them in the build output:

text
[INFO] --- compiler:3.13.0:compile (default-compile) @ hello ---
[INFO] --- surefire:3.2.5:test (default-test) @ hello ---
[INFO] --- jar:3.4.1:jar (default-jar) @ hello ---

Each line reads plugin:version:goal. The compiler plugin calls javac, Surefire runs tests, the jar plugin builds the JAR. Notice that our pom.xml mentions no plugins at all: the default ones are built into Maven's conventions. You declare a plugin in a <build><plugins> section only when you want to add something or change a default. Here is what such a section looks like (you will not need one in this module):

xml
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <version>3.4.1</version>
    </plugin>
  </plugins>
</build>

Plugins have coordinates too: the same groupId:artifactId:version idea.

Where the analogy stops

The recipe card lists steps. A pom.xml mostly lists facts (name, Java version, libraries); the steps come from Maven's built-in conventions. That is why a tiny pom.xml can drive a full build.

Examples

Example 1: sorting files into the layout (simple)

Where do these belong?

FileLocation
pom.xmlproject root
Greeter.java (package com.example)src/main/java/com/example/Greeter.java
GreeterTest.javasrc/test/java/com/example/GreeterTest.java
application.properties (config file)src/main/resources/
hello-1.0-SNAPSHOT.jartarget/ (generated, do not place it yourself)

Example 2: labeling a pom (practical)

xml
<groupId>com.shop</groupId>
<artifactId>orders</artifactId>
<version>2.3.0</version>
...
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.21.0</version>
</dependency>

The first three lines are this project's coordinates: com.shop:orders:2.3.0. The <dependency> block is a library, with its own coordinates org.apache.commons:commons-lang3:3.21.0. Same three fields, different roles: the project's own coordinates sit directly under <project>; dependency coordinates sit inside <dependency>.

Example 3: a minimal valid project (realistic)

The smallest pom.xml Maven accepts has just modelVersion and the three coordinates. This one passed mvn validate and then built:

xml
<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>demo</artifactId>
  <version>1.0</version>
</project>
text
[INFO] Building demo 1.0
[INFO] BUILD SUCCESS

Real projects add properties, dependencies, and sometimes plugins on top of this core. The minimal-element rule is described in Maven's POM introduction (see References).

Backend Insight

A backend repository is usually opened by many people over years. Because Maven projects share one layout, a new developer finds the code in src/main/java, the tests in src/test/java, and the dependencies in pom.xml without being told. The groupId also becomes the package prefix people recognize: when you see com.shop.orders in a stack trace, you know whose code it is. Later, when the project is packaged, the artifactId and version in the file name tell operations exactly which build is deployed.

Common Mistakes

Mistake 1: Wrong folder layout

  • Incorrect: tests in src/main/java, or sources in a folder called src with no main/java.
  • Correct: code in src/main/java, tests in src/test/java. Maven looks only there by default, so misplaced files are ignored or fail to compile (lesson 6 shows the real error for tests in the wrong tree).

Mistake 2: Putting the package folder name into src/main/java twice

If Greeter.java declares package com.example; it belongs in src/main/java/com/example/, not in src/main/java/main/java/com/example/.

Mistake 3: Not setting the Java version

Leaving out maven.compiler.release can compile for an old Java level and print obsolete warnings. Set it to the version of your JDK (21 in this course).

Mistake 4: Editing or committing target/

target/ is output. Edits there vanish at the next build, and committing it fills the repository with generated files (lesson 6 handles .gitignore).

Mistake 5: Mixing up the project's coordinates and a dependency's

A groupId directly under <project> names your project. A groupId inside <dependency> names a library. Changing the wrong one makes Maven look for your own project on the internet or rename your JAR.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. Write the folder where Order.java (package com.shop.model) belongs, and the folder where OrderTest.java (same package) belongs.

Show solution A1

A1. src/main/java/com/shop/model/Order.java and src/test/java/com/shop/model/OrderTest.java.

A2. What are the three coordinates? What does -SNAPSHOT mean?

Show solution A2

A2. groupId (owner), artifactId (project name), version. -SNAPSHOT marks a version that is still in development.

A3. Which folder holds everything Maven generates? Should it be committed to Git?

Show solution A3

A3. target/. No: it is generated and can always be rebuilt.

Level B: Application

B1. In the following fragment, identify the project's coordinates and the dependency's coordinates, and write the JAR file name Maven will build (the default packaging applies).

xml
<groupId>com.bank</groupId>
<artifactId>ledger</artifactId>
<version>0.4.1</version>
<dependencies>
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.21.0</version>
  </dependency>
</dependencies>
Show solution B1

B1. Project: com.bank:ledger:0.4.1. Dependency: org.apache.commons:commons-lang3:3.21.0. JAR: ledger-0.4.1.jar (artifactId, a dash, and the version).

B2. Your colleague's project compiles with warnings that "source value 8 is obsolete" although everyone runs JDK 21. Which part of the pom.xml is probably missing, and what would you add?

Show solution B2

B2. The Java version property. Add <properties><maven.compiler.release>21</maven.compiler.release></properties> (and the source encoding property).

B3. A pom.xml with no <groupId> fails. What is the message about, and what is the fix?

Show solution B3

B3. It says 'groupId' is missing (the project's own groupId, because it is a required coordinate). Add a <groupId>, for example com.example, next to artifactId and version.

Level C: Challenge

C1. Draw the full folder tree for a project inventory (com.shop:inventory:1.0) with two main classes Item and Stock, one test class StockTest, one configuration file app.properties, and the generated JAR.

Show solution C1

C1.

text
inventory/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/com/shop/Item.java
│   │   ├── java/com/shop/Stock.java
│   │   └── resources/app.properties
│   └── test/java/com/shop/StockTest.java
└── target/inventory-1.0.jar   (generated)

(The package com.shop is assumed from the groupId; any consistent package is acceptable.)

C2. A teammate says: "Our pom lists no plugins, so Maven can't compile or run tests." Explain why this is wrong.

Show solution C2

C2. Maven's default build uses default plugins that are bound to the phases by convention; they appear in the build output even though the pom.xml does not declare them. You declare a plugin only to add or change something.

Mini Challenge

Create a folder mini on your machine (anywhere outside your Git repositories), with a pom.xml containing coordinates com.example:mini:1.0-SNAPSHOT and the Java 21 properties from this lesson, and one class src/main/java/com/example/Mini.java that prints a line. Run mvn compile in that folder. If your Maven is not installed yet, check with mvn -v first and install it using the instructions on the Maven website; installation steps differ by operating system, so follow the official page for yours.

Check your work: the output ends with BUILD SUCCESS, and a target/classes/com/example/Mini.class file exists.

Summary & Next Lesson

Key takeaways

  • Maven expects pom.xml at the root, code in src/main/java, tests in src/test/java, and writes all output into target/.
  • The three coordinates groupId, artifactId, version identify a project (and every library).
  • A pom.xml has a modelVersion, coordinates, optional properties, dependencies, and, when needed, build/plugins.
  • Always set the Java version with maven.compiler.release.
  • Plugins do the real work; default ones are used without being declared.

Next lesson

The next lesson, "Dependencies and Maven Central", looks closely at the <dependencies> block: where the libraries come from, what scope means, and how to add one.

Self-check

  1. Where do test classes go in a Maven project?
  2. What are the three coordinates?
  3. Which folder can always be deleted safely?
  4. What does maven.compiler.release control?
  5. Do you have to declare the compiler plugin in your pom.xml?
Show answers
  1. src/test/java, in the same package folders as the code they test.
  2. groupId, artifactId, version.
  3. target/.
  4. The Java version used to compile the code.
  5. No. The default plugins are used automatically; you declare plugins only to add or change behavior.

References

  • Apache Maven, Introduction to the POM. Supports the minimal POM (modelVersion, groupId, artifactId, version), the default jar packaging, and the groupId:artifactId:version form.
  • Apache Maven, Introduction to the Standard Directory Layout. Supports the roles of src/main/java, src/test/java, src/main/resources, target, and pom.xml.
  • All build outputs and error messages shown were produced by Maven 3.9.11 on JDK 21 in a scratch project; plugin versions in the output are the defaults of that Maven version and may differ in yours. The meaning of -SNAPSHOT as "under development" is common Maven usage that I did not confirm on a fetched page.

Practice

Apply this lesson with the module's lab, exercises and quiz.

Lab and exercises Quiz

Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.

Continue to next lesson

Type to search levels, phases, modules and lessons.