The previous lesson established that JUnit 5 is three separate modules. That separation shows up immediately in the Maven setup: you need the right artifact, the right Surefire version, and — unlike TestNG — no XML suite file to get started. This lesson builds a project you can use for every exercise in this course, and explains why each configuration choice exists rather than just telling you what to copy.
The Maven dependency
JUnit 5 publishes a convenience artifact called junit-jupiter that bundles everything you need for writing and running Jupiter tests:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>This single dependency pulls in three things transitively: junit-jupiter-api (the annotations and assertion API you write against), junit-jupiter-engine (the TestEngine that executes Jupiter tests), and junit-jupiter-params (the parameterised test support). You can declare them separately for fine-grained control, but junit-jupiter is simpler and correct for most projects.
Two things worth noting: <scope>test</scope> keeps JUnit off your production classpath — same principle as TestNG. And the version 5.10.2 is the current stable release as of this writing; check central.sonatype.com for the latest.
The Surefire plugin — the critical piece
This is where most first-time JUnit 5 setups break. Maven ships with a bundled Surefire version that predates JUnit 5. That old version does not understand the JUnit Platform and will run zero tests — you'll see Tests run: 0, Failures: 0 with no error, which is the most confusing outcome possible. Fix it by declaring Surefire explicitly:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>Surefire 2.22.0 was the first version with JUnit Platform support. Version 3.2.5 is the current stable — use it. No extra configuration is needed; Surefire auto-detects the Jupiter engine on the classpath.
Full pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.tests</groupId>
<artifactId>junit5-suite</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>
</project>Java 17 is the minimum recommended version for JUnit 5.10. It is the current LTS and what most teams use. If your environment requires Java 11, change the compiler properties to 11 — everything in this course compiles on 11.
Your first test class
package com.mycompany.tests;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void addition() {
assertEquals(4, 2 + 2);
}
@Test
void division() {
assertEquals(2.5, 10.0 / 4.0, 0.001);
}
}Three things to notice that are different from TestNG and JUnit 4:
No public modifier. Jupiter does not require test classes or methods to be public. Package-private (no modifier) is fine and is the recommended style. This is a deliberate JUnit 5 design choice — access modifiers on test classes have no semantic meaning since tests are always called reflectively by the framework.
Static imports for assertions. Assertions.assertEquals(...) works, but import static org.junit.jupiter.api.Assertions.* is the convention. Every IDE will offer this as a quick-fix when you type assertEquals without an import.
No test runner annotation. In JUnit 4 you had @RunWith(...). In TestNG you had the testng.xml runner. In Jupiter there is nothing — the engine discovers any class containing @Test methods automatically.
Running tests
# Run everything
mvn clean test
# Run a single class
mvn test -Dtest=CalculatorTest
# Run a single method
mvn test -Dtest=CalculatorTest#addition
# Run all tests matching a pattern
mvn test -Dtest="Calculator*"In IntelliJ: right-click a method → Run. Right-click the class → Run. Right-click the src/test/java directory → Run All Tests. The JUnit 5 runner is built in — no plugin to install.
After a successful run you'll see:
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS
Surefire writes XML results to target/surefire-reports/. These are what Jenkins and GitHub Actions parse for test trend graphs.
The full setup flow
Step 1 of 6
Create Maven project
IntelliJ → File → New Project → Maven Archetype → maven-archetype-quickstart. GroupId: com.mycompany.tests, ArtifactId: junit5-suite. Or run: mvn archetype:generate -DgroupId=com.mycompany.tests -DartifactId=junit5-suite -DarchetypeArtifactId=maven-archetype-quickstart.
Project layout
junit5-suite/
├── pom.xml
└── src/
└── test/
├── java/
│ └── com/mycompany/tests/
│ ├── CalculatorTest.java
│ └── ...
└── resources/
└── testdata/ ← CSV files for parameterised tests
Unlike TestNG, there is no testng.xml equivalent. JUnit 5 discovers tests by scanning for @Test methods. You can add a junit-platform.properties file to src/test/resources/ for configuration options like parallel execution (covered in Chapter 4), but it is not required to get started.
⚠️ Common mistakes
Tests run: 0after adding JUnit 5. This almost always means Surefire is the bundled old version that doesn't know the JUnit Platform. Add<version>3.2.5</version>to the Surefire plugin declaration and re-run. Second possibility: your test class or method name doesn't end inTestor start withTest— JUnit 5 discovers by annotation, not name, but Surefire's class-scanner still applies its default pattern. Move the class to a properly named file or add**/*Test.javato Surefire's includes.publicon every method out of JUnit 4 habit. It compiles fine, but Jupiter neither requires nor benefits from it. Droppingpublicis a visible signal that this is JUnit 5 code, not migrated JUnit 4.- Missing
import static org.junit.jupiter.api.Assertions.*. IfassertEqualsdoesn't resolve and IntelliJ offersorg.junit.Assert, you have a JUnit 4 jar on the classpath (perhaps from an archetype). Add the static import from Jupiter and remove the JUnit 4 jar.
🎯 Practice task
Build the skeleton you'll use for the whole course. 20–25 minutes.
- Create
junit5-suitewith thepom.xmlshown. Runmvn dependency:tree— confirm you seejunit-jupiter-5.10.2,junit-jupiter-api,junit-jupiter-engine, andjunit-jupiter-paramsall resolved. - Write
CalculatorTest.javawith four test methods: addition, subtraction, multiplication, division. Runmvn clean test. Confirm four tests pass. - Force a failure on one test. Read the console output and find the exact line that says which method failed and what the mismatch was. Revert.
- Try the name-pattern filter. Run
mvn test -Dtest=CalculatorTest#addition. Confirm only one test runs. - Open
target/surefire-reports/. Find the XML file forCalculatorTest. Open it and read the structure — this is what CI tools parse for test reporting. - Stretch — add a second test class. Create
StringUtilsTest.javawith two tests. Runmvn testand confirm both classes execute without any configuration change.
Next lesson: writing meaningful tests with @Test and the full assertion toolkit.