Getting started with JUnit 6 is straightforward, but doing it correctly β with the right dependencies, the right project structure, and a clear understanding of what each piece does β sets you up for success in everything that follows. This guide walks you through every step: from zero to a running, passing test.
Whether you are using Maven or Gradle, IntelliJ IDEA or Eclipse, this post has you covered with exact configuration, common pitfalls, and annotated code examples.
What Is JUnit 6 and Why Use It?
JUnit 6 is the latest major release of the JUnit testing framework for Java. It builds on JUnit 5βs architecture β the three-module design of Platform, Jupiter, and Vintage β and adds improvements to its extension model, parameterized testing APIs, and lifecycle management.
Unit testing allows you to verify that individual pieces of your code (units) behave correctly in isolation. JUnit makes this easy with annotations like @Test, @BeforeEach, and a rich library of assertion methods.
System Requirements
Before installing JUnit 6, make sure your environment meets these requirements:
- Java 11 or higher (Java 17 LTS or Java 21 LTS recommended)
- Maven 3.8+ or Gradle 7.6+
- An IDE: IntelliJ IDEA 2023+, Eclipse 2023+, or VS Code with the Java Extension Pack
Verify your Java version by running:
java -version
# Output example:
# openjdk version "17.0.10" 2024-01-16
# OpenJDK Runtime Environment (build 17.0.10+7)
# OpenJDK 64-Bit Server VM (build 17.0.10+7, mixed mode, sharing)
Option 1: Setting Up JUnit 6 with Maven
Maven is the most common build tool for Java projects. Create a new Maven project or open your existing pom.xml and add the following:
<?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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>junit6-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<!-- Use Java 17 LTS -->
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<junit.version>6.0.0</junit.version>
</properties>
<dependencies>
<!-- JUnit Jupiter: the test writing API -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Surefire Plugin: required to run JUnit 6 tests -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>
</project>
Key points about this configuration:
- The
junit-jupiterartifact is a convenient aggregator that brings injunit-jupiter-api,junit-jupiter-params, andjunit-jupiter-enginein one dependency. - The
scopeis set totestso JUnit is only on the test classpath β it will not be bundled into your production JAR. - Maven Surefire Plugin 3.x is required β older versions do not support JUnit 6 natively.
Option 2: Setting Up JUnit 6 with Gradle
For Gradle projects, open your build.gradle (Groovy DSL) or build.gradle.kts (Kotlin DSL) and add:
// build.gradle (Groovy DSL)
plugins {
id 'java'
}
group = 'com.example'
version = '1.0-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
// JUnit Jupiter: aggregator dependency for writing and running tests
testImplementation 'org.junit.jupiter:junit-jupiter:6.0.0'
}
test {
// IMPORTANT: This line tells Gradle to use the JUnit Platform test engine
useJUnitPlatform()
// Print a summary after all tests run
testLogging {
events 'passed', 'failed', 'skipped'
}
}
The useJUnitPlatform() line is critical β without it, Gradle will not discover or run your JUnit 6 tests.
Project Structure
The standard Maven/Gradle project layout separates production code from test code:
junit6-demo/
βββ src/
β βββ main/
β β βββ java/
β β βββ com/example/ <-- your production code
β βββ test/
β βββ java/
β βββ com/example/ <-- your test classes go here
βββ pom.xml (or build.gradle)
βββ README.md
By convention, test classes mirror the production package structure and are named with a Test suffix (e.g., CalculatorTest tests Calculator).
Writing Your First JUnit 6 Test
Letβs create a simple Calculator class to test, then write the test class.
Step 1: Create the production class
Create the file src/main/java/com/example/Calculator.java:
package com.example;
/**
* A simple Calculator class demonstrating basic arithmetic operations.
* This is the class we will test using JUnit 6.
*/
public class Calculator {
/**
* Returns the sum of two integers.
*/
public int add(int firstNumber, int secondNumber) {
return firstNumber + secondNumber;
}
/**
* Returns the difference of two integers.
*/
public int subtract(int firstNumber, int secondNumber) {
return firstNumber - secondNumber;
}
/**
* Returns the product of two integers.
*/
public int multiply(int firstNumber, int secondNumber) {
return firstNumber * secondNumber;
}
/**
* Divides the first integer by the second.
* @throws ArithmeticException if the divisor is zero
*/
public double divide(int dividend, int divisor) {
if (divisor == 0) {
throw new ArithmeticException("Division by zero is not allowed");
}
return (double) dividend / divisor;
}
}
Step 2: Create the test class
Create the file src/test/java/com/example/CalculatorTest.java:
package com.example;
// JUnit 6 imports
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
// Static import for all assertion methods
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for the Calculator class.
*
* Key JUnit 6 features demonstrated:
* - @Test marks a method as a test case
* - @BeforeEach runs setup before each test
* - @DisplayName gives tests a readable description
* - assertEquals, assertThrows from Assertions
*/
@DisplayName("Calculator Unit Tests")
class CalculatorTest {
// The object under test
private Calculator calculator;
/**
* @BeforeEach runs before EVERY test method in this class.
* Use it to set up fresh objects so tests don't share state.
*/
@BeforeEach
void setUp() {
calculator = new Calculator();
}
// ----------------------------------------------------------------
// Addition Tests
// ----------------------------------------------------------------
@Test
@DisplayName("Adding two positive numbers returns their sum")
void addingTwoPositiveNumbers() {
// Arrange: inputs are defined directly
int firstNumber = 7;
int secondNumber = 3;
// Act: call the method under test
int result = calculator.add(firstNumber, secondNumber);
// Assert: verify the result
assertEquals(10, result, "7 + 3 should equal 10");
}
@Test
@DisplayName("Adding a positive and negative number returns correct result")
void addingPositiveAndNegativeNumber() {
assertEquals(-1, calculator.add(4, -5), "4 + (-5) should equal -1");
}
@Test
@DisplayName("Adding zero to any number returns the same number")
void addingZeroReturnsIdentity() {
assertEquals(42, calculator.add(42, 0), "42 + 0 should equal 42");
}
// ----------------------------------------------------------------
// Division Tests
// ----------------------------------------------------------------
@Test
@DisplayName("Dividing two numbers returns the correct quotient")
void dividingTwoNumbers() {
double result = calculator.divide(10, 4);
// assertEquals with delta for floating-point comparisons
assertEquals(2.5, result, 0.001, "10 / 4 should equal 2.5");
}
@Test
@DisplayName("Dividing by zero throws ArithmeticException")
void dividingByZeroThrowsArithmeticException() {
// assertThrows verifies that the expected exception type is thrown
// and returns the exception so you can inspect its message
ArithmeticException thrownException = assertThrows(
ArithmeticException.class,
() -> calculator.divide(10, 0),
"Dividing by zero should throw ArithmeticException"
);
// Optionally verify the exception message
assertEquals("Division by zero is not allowed", thrownException.getMessage());
}
// ----------------------------------------------------------------
// Multiple Assertions in One Test
// ----------------------------------------------------------------
@Test
@DisplayName("assertAll runs all assertions even if one fails")
void multipleAssertionsWithAssertAll() {
// assertAll groups assertions: all run even if the first fails
assertAll("calculator operations",
() -> assertEquals(5, calculator.add(2, 3), "2 + 3"),
() -> assertEquals(1, calculator.subtract(4, 3), "4 - 3"),
() -> assertEquals(12, calculator.multiply(3, 4), "3 * 4")
);
}
}
Running the Tests
From the Command Line
# Maven
mvn test
# Gradle
./gradlew test
Expected Output
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.CalculatorTest
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.312 s
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] BUILD SUCCESS
Test Results (with @DisplayName labels):
β Adding two positive numbers returns their sum
β Adding a positive and negative number returns correct result
β Adding zero to any number returns the same number
β Dividing two numbers returns the correct quotient
β Dividing by zero throws ArithmeticException
β assertAll runs all assertions even if one fails
From IntelliJ IDEA
Right-click on the test class or any test method β Run βCalculatorTestβ. The test runner panel at the bottom shows green checkmarks for passing tests and red Xs with stack traces for failures.
Understanding the Output: What Happens When a Test Fails
Letβs intentionally break a test to see what the failure output looks like. Change assertEquals(10, ...) to assertEquals(99, ...):
[ERROR] Tests run: 6, Failures: 1, Errors: 0, Skipped: 0
[ERROR] CalculatorTest.addingTwoPositiveNumbers -- Time elapsed: 0.043 s <<< FAILURE!
org.opentest4j.AssertionFailedError: 7 + 3 should equal 10
expected: <99>
but was: <10>
at com.example.CalculatorTest.addingTwoPositiveNumbers(CalculatorTest.java:45)
The output tells you exactly what was expected (99), what was actually returned (10), and the exact line number in your test file. This makes debugging test failures fast and precise.
Key Annotations Introduced in This Guide
@Testβ Declares a method as a JUnit 6 test. The method must returnvoidand must not bestatic.@BeforeEachβ Runs the annotated method before each individual@Testmethod. Ideal for resetting shared state.@DisplayNameβ Attaches a human-readable name to a test class or test method. Shown in IDE and HTML reports.
Frequently Asked Questions (FAQs)
Q1: Why does my Maven build not find my JUnit 6 tests?
The most common cause is an outdated Maven Surefire Plugin. Versions older than 2.22.0 do not support the JUnit Platform. Add or update the plugin to version 3.2.5 or higher in your pom.xml. Also make sure your test classes follow the naming convention (*Test.java, Test*.java, or *Tests.java).
Q2: What is the difference between junit-jupiter-api and junit-jupiter?
junit-jupiter-api contains only the annotations and assertion classes you use when writing tests. junit-jupiter (without a suffix) is an aggregator POM that also includes junit-jupiter-params (for parameterized tests) and junit-jupiter-engine (the runtime engine). For most projects, using the aggregator junit-jupiter is the simplest choice.
Q3: My test class has no public modifier. Is that correct?
Yes, that is intentional in JUnit 6. Test classes and methods do not need to be public. JUnit 6 uses reflection to discover and run them, so package-private access is sufficient. This removes unnecessary boilerplate.
Q4: Can I use JUnit 6 with an older version of Java?
JUnit 6 requires Java 11 or higher. If you are on Java 8 or Java 10, you will need to use JUnit 5 instead. It is strongly recommended to upgrade to at least Java 11 (or preferably Java 17 LTS) to take full advantage of JUnit 6βs features and the modern Java ecosystem.
Q5: How do I run only a specific test method from the command line?
With Maven, use the -Dtest flag:
# Run a specific test class
mvn test -Dtest=CalculatorTest
# Run a specific test method
mvn test -Dtest=CalculatorTest#addingTwoPositiveNumbers
With Gradle, use the --tests flag:
./gradlew test --tests "com.example.CalculatorTest.addingTwoPositiveNumbers"
See Also
- JUnit 6 Tutorial: From Zero to Advanced (Complete Series Index)
- JUnit 6 with Maven and Gradle: Complete Setup Guide
- JUnit 6 Assertions: All Methods Explained with Real Examples
- JUnit 6 Test Lifecycle Explained (BeforeEach, AfterAll, and More)
- JUnit 6 vs JUnit 5: Key Differences and Migration Guide
Conclusion
You have now installed JUnit 6, set up both Maven and Gradle configurations, created a production class, written a full test class with six different test cases, and run them successfully. You have also seen what a test failure looks like and how to read the error output.
This is your foundation. Every advanced JUnit 6 feature β parameterized tests, extensions, Spring Boot integration β builds on exactly what you learned here.
Next up: JUnit 6 with Maven and Gradle: Complete Setup Guide for a deeper look at build tool configuration, multi-module projects, and IDE integration.