Demo: converting a project to Maven
On This Lesson 11 topics
Lesson Overview
This lesson puts the whole module together. You will take a plain project, the kind you built in the data-structures modules with an IDE and a JUnit library added by hand (the approach BEG-045 used), and convert it to Maven, step by step, until mvn test passes in the terminal. We will make one planned mistake on purpose, because you will make it yourself sooner or later, and it is better to have seen its message first.
This is the preparation for the module lab, Mavenize: converting your data-structures project to Maven with JUnit as a test dependency, so that mvn test passes from the terminal and the project builds on a clean machine.
To keep every command visible, the demo uses a small stand-in project: a class MyArrayList and its JUnit test, in package ds. Your real project is bigger, but the steps are identical. This lesson builds on lessons 1 to 5.
Learning Objectives
By the end of this lesson you will be able to:
- Convert a plain-folder Java project into a Maven project in five steps.
- Write a minimal
pom.xmlwith coordinates and a Java version. - Move sources to
src/main/javaand tests tosrc/test/javawithout breaking package structure. - Add JUnit as a
testdependency and recognize the error when a test sits in the wrong tree. - Ignore
target/with.gitignoreand check that a fresh copy of the project builds. - Describe how the lab's acceptance rule ("builds on a clean machine") is checked.
Written Explanation
The starting point
Before conversion, the project looks like this (the IDE layout many beginners have, with sources and tests mixed together):
data-structures/
└── src/
└── ds/
├── MyArrayList.java
└── MyArrayListTest.javaMyArrayList stores ints in an array that grows when full, and MyArrayListTest has two JUnit tests: that adding five elements works past the starting capacity, and that get rejects a bad index with IndexOutOfBoundsException. The JUnit library was added through the IDE and lives in IDE settings, not in a file inside the project. That is exactly the "knowledge in somebody's head" problem from lesson 1.
The five steps
- Create
pom.xmlat the project root with coordinates and the Java version. - Move sources to
src/main/javaand tests tosrc/test/java, keeping the package folders. - Planned mistake: leave the test class in
src/main/javaand see what breaks. - Add JUnit as a
testdependency. - Run
mvn test, addtarget/to.gitignore, and commit.
Step 1: the pom.xml
Create the file next to the src folder. This is the whole file at this point:
<?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>data-structures</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>Choose your own groupId (a name you control, often a reversed domain; com.example is the conventional placeholder). Use the Java version of your JDK (21 here).
Step 2: move the files
Create src/main/java and src/test/java, then move the package folder ds (not just the files) so the package structure stays intact. Use your IDE's move or refactor tools if you prefer; they update nothing in the code here because the package names do not change. The resulting layout:
data-structures/
├── pom.xml
└── src/
├── main/java/ds/MyArrayList.java
└── test/java/ds/MyArrayListTest.javaIf you move files in the terminal, use the commands from module BEG-006 (mkdir, mv) and keep the ds/ folder name.
Step 3: the planned mistake
Suppose you moved everything into src/main/java, tests included, which is easy to do when you think "put the code in main". Run mvn test. Real output (trimmed):
[ERROR] COMPILATION ERROR :
[ERROR] .../src/main/java/ds/MyArrayListTest.java:[3,29] package org.junit.jupiter.api does not exist
[ERROR] .../src/main/java/ds/MyArrayListTest.java:[7,6] cannot find symbolTwo things to notice. First, the message complains about the JUnit import, which sends many learners to the wrong place: "I need to add JUnit!" Second, the file path in the message ends in src/main/java. That is the clue. Code in src/main/java is compiled without test libraries by design: the main code must not depend on testing tools.
To confirm that, we added the test-scoped JUnit dependency (step 4) while the test class was still in src/main/java, and ran mvn test again. The same error appeared: package org.junit.jupiter.api does not exist. The test scope really is invisible to main code, as lesson 3 promised.
The fix is to move the test class to src/test/java/ds/. Then the main tree contains only MyArrayList.java.
Step 4: add JUnit as a test dependency
Add this block to the pom.xml, after </properties>:
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.12.2</version>
<scope>test</scope>
</dependency>
</dependencies>(Check the current version on Maven Central, as in lesson 3. The version shown is the one used in this module's runs.) We also tried moving the test into src/test/java before adding the dependency: the error stayed the same, because the library was still missing. Both fixes are needed: right folder and right dependency.
Step 5: run it, ignore target/, commit
Now mvn test. Real output:
[INFO] Running ds.MyArrayListTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.105 s -- in ds.MyArrayListTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESSTwo tests, as we wrote. The build created a target/ folder, which must stay out of version control (module BEG-046 introduced Git and branching). Create a file named .gitignore in the project root with this line:
target/The trailing slash means "a folder". This is a plain text file; Git reads it and leaves target/ out of git status and out of commits. (I did not run Git in this demo, so the Git side of the check is described, not shown: after adding the line, run git status and confirm target/ is not listed before you commit pom.xml, .gitignore, and src/.)
The clean-machine check
The lab says "builds on a clean machine". You can approximate that without a second computer: copy only what is committed (pom.xml, .gitignore, src/, in a real project the output of git clone) into a new empty folder and run mvn test there. We did exactly this with a folder that contained those three items, and got Tests run: 2, Failures: 0 and BUILD SUCCESS. If it fails, something your build needs lives outside the repository: a JAR in your IDE settings, a file you forgot to commit, or a path that exists only on your machine. A truly clean machine also downloads the libraries again, since its local repository is empty, which needs internet access on the first build.
Where the analogy stops
Converting is not just "move files". The original project was held together by IDE settings you could not see; Maven's convention makes everything visible in a few text files, and that is the point. But Maven will not fix your code: if your tests were already failing, they still fail after the conversion.
Examples
Example 1: only a Maven layout change (simple)
A project with src/Hello.java and no libraries. Conversion: add a pom.xml with com.example:hello:1.0-SNAPSHOT and Java 21; move Hello.java to src/main/java/Hello.java (no package, so no sub-folders). mvn compile gives BUILD SUCCESS. Classes without a package are legal in Maven, but real projects use packages, so add one when you can.
Example 2: the demo project, before and after (practical)
Before: src/ds/MyArrayList.java and src/ds/MyArrayListTest.java, JUnit in IDE settings. After: the layout from step 2, a pom.xml with a test-scoped dependency, and a .gitignore containing target/. The IDE button and mvn test now both work, and mvn test works for everyone else too.
Example 3: a project that already uses a library (realistic)
Say your project also imports org.apache.commons.lang3.StringUtils, with its JAR copied into a lib/ folder. Convert it like this: delete the JAR copy, add Commons Lang as a normal (compile scope) dependency from lesson 3, and let Maven download it. Do not commit the JAR. The build now needs no file from lib/, which is what "builds on a clean machine" demands.
Backend Insight
Converting an old project is a task you will meet at work: a team inherits code built with scripts or IDE settings and moves it to Maven so that CI and new hires can build it. The steps are always the same: coordinates, layout, dependencies, a green mvn test on a clean checkout, and a .gitignore for output. The "clean checkout" test is the one that counts, because it is what the build server does. Later you will meet larger projects with several modules, and tools such as the Maven wrapper script that pins the Maven version itself; for now, the single-module conversion is the foundation.
Common Mistakes
Mistake 1: Wrong folder layout
- Incorrect: tests in
src/main/java, or sources left insrc/ds. - Correct:
src/main/java/<package>for code,src/test/java/<package>for tests. The tell-tale of tests in main is the "package org.junit.jupiter.api does not exist" error from a file undersrc/main.
Mistake 2: Committing target/
It contains generated files and bloats the repository. Add target/ to .gitignore before the first commit. If it has already been committed, .gitignore alone will not remove it: Git keeps tracking files it already knows (ask a teammate or module BEG-046's material on how to untrack them).
Mistake 3: Copying the JUnit JAR into the project
The point of the conversion is that Maven fetches it. Remove manual JAR copies.
Mistake 4: Forgetting maven.compiler.release
You will get old-version warnings or newer language features may fail to compile (lesson 2).
Mistake 5: Believing "it passes in the IDE" is enough
Run mvn test in a terminal, from a clean copy of the project. That is the acceptance rule of the lab.
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. List the five conversion steps in order.
Show solution A1
A1. Create pom.xml; move sources and tests into src/main/java and src/test/java; (planned mistake: tests in the main tree fail to compile); add JUnit as a test dependency; run mvn test, add target/ to .gitignore, commit.
A2. Where must MyArrayListTest.java (package ds) live in a Maven project?
Show solution A2
A2. src/test/java/ds/MyArrayListTest.java.
A3. What goes into .gitignore to keep Maven's output out of Git?
Show solution A3
A3. The line target/.
Level B: Application
B1. Convert this small project on paper: src/app/Calc.java, src/app/CalcTest.java, JUnit configured in the IDE. Write the final folder tree and the <dependency> block you add.
Show solution B1
B1. Tree: pom.xml, src/main/java/app/Calc.java, src/test/java/app/CalcTest.java. Dependency: the junit-jupiter block from step 4 with <scope>test</scope> (use the current version from Central).
B2. After moving a test to src/test/java, mvn test still says package org.junit.jupiter.api does not exist. What did you forget?
Show solution B2
B2. The dependency block for JUnit in pom.xml. Moving the file fixes the folder, but the library still has to be declared (we saw the same error with the right folder and no dependency).
B3. Your friend ran mvn test and got BUILD SUCCESS, but the output shows no Tests run: line. Name one likely cause.
Show solution B3
B3. The test classes are not found: they may be outside src/test/java, or their names may not match Surefire's patterns such as *Test (lesson 5).
Level C: Challenge
C1. Take any small project of yours with a JUnit test and convert it, following the five steps. Keep a short log: for each step, the command or edit, and the result. Finish with a build from a copy of the project made without target/.
Show solution C1
C1. Check your log against the demo: step 3 is optional (you do not have to repeat the planned mistake); your final mvn test should end with BUILD SUCCESS and a Tests run: total equal to the number of tests you wrote.
C2. Explain, using this lesson, why the test scope on JUnit made the planned mistake fail even after JUnit was added.
Show solution C2
C2. The test scope means the library is available only to test code. A class in src/main/java is main code, so JUnit is invisible to it. The class had to move to src/test/java.
Mini Challenge
Do the lab preparation for real. Pick the data-structures project you will use for BEG-054-LAB1 and convert it now, in a copy of the folder, without deleting the original. Aim for: a pom.xml with your own coordinates, tests in src/test/java, JUnit as a test dependency, target/ in .gitignore, and mvn test green. Finish by running mvn test in a fresh copy of the repository's files. Replace the manual command list you wrote in lesson 1's Mini Challenge with the one command you now need.
Check your work: the fresh copy builds with BUILD SUCCESS, the Tests run: total equals your number of tests, and git status (when you use Git) does not list target/.
Summary & Next Lesson
Key takeaways
- Converting to Maven:
pom.xml, move to the standard layout, add JUnit as atestdependency, runmvn test, ignoretarget/. - Tests in
src/main/javafail with a message about the JUnit import; the test scope is not visible to main code. - Both the right folder and the right dependency are needed.
target/belongs in.gitignorebefore the first commit.- The real check is a clean copy of the project building with one command.
Next lesson
This was the last lesson of the module. Finish the lab, Mavenize, then continue with the next module in the course.
Self-check
- Where do tests go in a Maven project?
- What scope does JUnit get, and what does it mean?
- Why does a test in
src/main/javafail to compile? - What line goes into
.gitignore? - How can you check that the project builds on a clean machine?
Show answers
src/test/java.test: available only when compiling and running tests.- Main code is compiled without test-scope libraries, so the JUnit imports cannot be found.
target/.- Make a fresh copy or clone containing only the committed files and run
mvn testthere; it should end withBUILD SUCCESS.
References
- Apache Maven, Introduction to the Standard Directory Layout. Supports
src/main/java,src/test/java,target, andpom.xmlat the root. - Apache Maven, Introduction to the Dependency Mechanism. Supports the
testscope (available only for test compilation and execution). - Every command and message shown was run with Maven 3.9.11 and JDK 21 on a stand-in project in a scratch folder. The
.gitignoreeffect ongit status, and how to untrack already-committed files, are stated from general Git knowledge and were not run here.
Practice
Apply this lesson with the module's lab, exercises and quiz.
Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.