Maven Project Structure & pom.xml
Goal: Be able to look at a Maven project and understand what everything means.
In the previous page, we saw why Maven exists. Now let's open our Student Report App as a real Maven project and understand every folder and file inside it.
Standard Maven folder structure
Every Maven project follows the same folder layout:
1student-report-app/2├── pom.xml3├── src/4│ ├── main/5│ │ ├── java/6│ │ │ └── com/example/report/7│ │ │ ├── Main.java8│ │ │ ├── Student.java9│ │ │ └── ReportWriter.java10│ │ └── resources/11│ │ └── app.properties12│ └── test/13│ ├── java/14│ │ └── com/example/report/15│ │ └── ReportWriterTest.java16│ └── resources/17│ └── sample-students.json18└── target/The idea is simple: main is for your app, test is for testing your app. Inside each, java holds code, and resources holds everything else.
Maven expects this layout by default. You don't have to tell Maven where your code is, because it already knows where to look. This idea is called convention over configuration: follow the standard, and you skip the setup.
It also means any Java developer can open any Maven project and immediately know where things are.
Let's go through each folder.
src/main/java
This is where your application code lives: the code that ends up in the final JAR.
For our app, that's Main.java, Student.java, and ReportWriter.java.
The folders inside match the package name. If a class starts with:
1package com.example.report;then the file must be inside src/main/java/com/example/report/.
Maven compiles everything here into .class files, the same .java → .class step from the previous page, but done for you.
src/main/resources
This folder holds non-code files your app needs while running, such as:
- configuration files (
app.properties) - templates
- text or data files
Our app keeps its settings in app.properties:
1report.title=Student Marks Report2report.output=report.jsonMaven copies these files as they are (no compiling) and puts them next to your .class files. So they end up inside the final JAR, and on the classpath.
That's why your code can load them from the classpath instead of using a file path:
1InputStream in = Main.class.getResourceAsStream("/app.properties");This works the same on every machine, because the file travels inside the JAR.
src/test/java
This is where your test code lives.
For our app, ReportWriterTest.java checks that ReportWriter creates correct JSON. Test classes usually use the same package as the code they test, so the folder structure mirrors src/main/java:
1src/main/java/com/example/report/ReportWriter.java2src/test/java/com/example/report/ReportWriterTest.javaTwo important rules:
- Test code is never included in the final JAR. Users of your app don't need your tests.
- By default, Maven runs test classes whose names end with
Test(likeReportWriterTest). Following this naming makes sure your tests actually run.
src/test/resources
This holds files used only by tests, like sample data.
Our test needs some fake student data to work with, so we keep it in sample-students.json:
1[2 { "name": "Asha", "marks": 92 },3 { "name": "Ravi", "marks": 78 }4]Like test code, these files are available only while tests run and are not added to the final JAR.
So far, all four folders hold things you write. The next folder is different: it holds things Maven creates.
target/
target/ is Maven's output folder. Everything Maven builds goes here.
After building our app, it looks like this:
1target/2├── classes/3│ ├── com/example/report/Main.class4│ ├── com/example/report/Student.class5│ ├── com/example/report/ReportWriter.class6│ └── app.properties7├── test-classes/8│ ├── com/example/report/ReportWriterTest.class9│ └── sample-students.json10└── student-report-app-1.0.0-SNAPSHOT.jarNotice where everything went:
| You wrote it in | Maven put it in | In final JAR? |
|---|---|---|
src/main/java | target/classes (compiled) | Yes |
src/main/resources | target/classes (copied) | Yes |
src/test/java | target/test-classes (compiled) | No |
src/test/resources | target/test-classes (copied) | No |
Key things to remember:
- Never edit files in
target/. Maven overwrites them on every build. - It is safe to delete. Maven recreates it on the next build.
- Don't commit it to Git. Add
target/to your.gitignore. Your team builds their own.
Notice the JAR's name: student-report-app-1.0.0-SNAPSHOT.jar. Where did Maven get that name? From pom.xml, the file we haven't opened yet.
What is pom.xml?
pom.xml is the heart of a Maven project. POM stands for Project Object Model.
It is one XML file at the root of the project that tells Maven:
- who the project is (its name and version),
- what it needs (its dependencies, like Jackson),
- how to build it (Java version and other build settings).
In the previous page, we saw that Maven builds the classpath and downloads libraries for us. pom.xml is where we tell Maven what to do. The folders tell Maven where things are; pom.xml tells it everything else.
It all starts with the project's identity, which Maven describes using coordinates.
Maven coordinates: groupId, artifactId, version, packaging
Every Maven project is described by a few values called coordinates. Think of them like a postal address: together, they point to exactly one project.
groupId: who made it.
Usually your organization's domain name, written in reverse.
1<groupId>com.example</groupId>artifactId: the project's name.
Short, lowercase, words separated by hyphens.
1<artifactId>student-report-app</artifactId>version: which release.
1<version>1.0.0-SNAPSHOT</version>A version ending in -SNAPSHOT means "still in development, may change." When you release it, you remove -SNAPSHOT and it becomes a fixed version like 1.0.0, which should never change again.
packaging: what kind of file to build.
1<packaging>jar</packaging>| Packaging | Builds | Used for |
|---|---|---|
jar | a .jar file | Applications and libraries (the default) |
war | a .war file | Web apps deployed to a server like Tomcat |
pom | no code file | Parent projects that group other projects |
If you leave out packaging, Maven uses jar.
Now you can see where the JAR's name came from:
1artifactId + "-" + version + ".jar"2student-report-app-1.0.0-SNAPSHOT.jarThese look familiar for a reason: they are the same coordinates we used to add Jackson in the previous page. Jackson is just another Maven project with its own groupId, artifactId, and version.
Let's put it all together into a complete pom.xml.
Basic pom.xml
Here is the full pom.xml for our Student Report App:
1<?xml version="1.0" encoding="UTF-8"?>2<project xmlns="http://maven.apache.org/POM/4.0.0"3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">5 6 <modelVersion>4.0.0</modelVersion>7 8 <!-- Who this project is -->9 <groupId>com.example</groupId>10 <artifactId>student-report-app</artifactId>11 <version>1.0.0-SNAPSHOT</version>12 <packaging>jar</packaging>13 14 <!-- Build settings -->15 <properties>16 <maven.compiler.release>17</maven.compiler.release>17 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>18 </properties>19 20 <!-- What this project needs -->21 <dependencies>22 <dependency>23 <groupId>com.fasterxml.jackson.core</groupId>24 <artifactId>jackson-databind</artifactId>25 <version>2.17.0</version>26 </dependency>27 28 <dependency>29 <groupId>org.junit.jupiter</groupId>30 <artifactId>junit-jupiter</artifactId>31 <version>5.10.2</version>32 <scope>test</scope>33 </dependency>34 </dependencies>35 36</project>What each part means:
| Part | Meaning |
|---|---|
<project ...> | The root tag. The long attributes just tell editors which XML format this is. You can copy them as-is. |
modelVersion | The version of the POM format. It is always 4.0.0. |
groupId, artifactId, version, packaging | The project's coordinates: who it is. |
maven.compiler.release | Which Java version to compile for (here, Java 17). |
project.build.sourceEncoding | Read source files as UTF-8, so special characters work on every machine. |
dependencies | The libraries this project needs. |
jackson-databind | Our JSON library. Maven also downloads jackson-core and jackson-annotations for it automatically. |
junit-jupiter with scope test | The testing library. test scope means it is used only for code in src/test and is not part of the final app. |
Notice how small this file is. Compare it to the manual work from the previous page: downloading three JARs, checking versions, writing a long classpath. All of that is replaced by these few lines.
Notice also that every dependency is written using coordinates. That's no accident; coordinates are how Maven finds and tells projects apart.
How Maven identifies a project
Maven identifies every project using three coordinates together:
1groupId : artifactId : versionThis is often called GAV. Our project's GAV is:
1com.example:student-report-app:1.0.0-SNAPSHOTAnd Jackson's GAV is:
1com.fasterxml.jackson.core:jackson-databind:2.17.0No two projects in the world should share the same GAV. That's why groupId uses a domain name: you own your domain, so nobody else should use your groupId.
Each part narrows it down:
1com.example → all projects from example.com2com.example:student-report-app → one specific project3...:1.0.0-SNAPSHOT → one specific version of itMaven also uses the GAV to decide where to store files on your computer. Every downloaded library is saved in a local folder called ~/.m2/repository, and the path is built from the GAV:
1~/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.17.0/jackson-databind-2.17.0.jar2 └──────── groupId ───────┘ └── artifactId ─┘ └version┘The dots in groupId become folders. So once Maven downloads Jackson, every project on your computer can reuse the same copy, with no more copying JARs into lib folders.
Here's the big picture: in Maven's world, your project and Jackson are the same kind of thing. Both are identified by a GAV. Both can be built into a JAR. And just as your app depends on Jackson, another project could one day depend on com.example:student-report-app.
Now you can open any Maven project, read its folders and its pom.xml, and understand what everything means. Next, we'll learn the commands that turn this project into a working JAR.