Skip to content
Ameen Abozaid
PublishedBEG-054-L06

Demo: converting a project to Maven

  • Lesson 6 of 6
  • 10 min read
  • Beginner
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.xml with coordinates and a Java version.
  • Move sources to src/main/java and tests to src/test/java without breaking package structure.
  • Add JUnit as a test dependency and recognize the error when a test sits in the wrong tree.
  • Ignore target/ with .gitignore and 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):

text
data-structures/
└── src/
    └── ds/
        ├── MyArrayList.java
        └── MyArrayListTest.java

MyArrayList 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

  1. Create pom.xml at the project root with coordinates and the Java version.
  2. Move sources to src/main/java and tests to src/test/java, keeping the package folders.
  3. Planned mistake: leave the test class in src/main/java and see what breaks.
  4. Add JUnit as a test dependency.
  5. Run mvn test, add target/ 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
<?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:

text
data-structures/
├── pom.xml
└── src/
    ├── main/java/ds/MyArrayList.java
    └── test/java/ds/MyArrayListTest.java

If 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):

text
[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 symbol

Two 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>:

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

text
[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 SUCCESS

Two 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:

text
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 in src/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 under src/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 a test dependency, run mvn test, ignore target/.
  • Tests in src/main/java fail 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 .gitignore before 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

  1. Where do tests go in a Maven project?
  2. What scope does JUnit get, and what does it mean?
  3. Why does a test in src/main/java fail to compile?
  4. What line goes into .gitignore?
  5. How can you check that the project builds on a clean machine?
Show answers
  1. src/test/java.
  2. test: available only when compiling and running tests.
  3. Main code is compiled without test-scope libraries, so the JUnit imports cannot be found.
  4. target/.
  5. Make a fresh copy or clone containing only the committed files and run mvn test there; it should end with BUILD SUCCESS.

References

  • Apache Maven, Introduction to the Standard Directory Layout. Supports src/main/java, src/test/java, target, and pom.xml at the root.
  • Apache Maven, Introduction to the Dependency Mechanism. Supports the test scope (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 .gitignore effect on git 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.

Lab and exercises Quiz

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

Type to search levels, phases, modules and lessons.