JUnit 6 with Maven and Gradle: Complete Setup Guide

Setting up JUnit 6 with Maven and Gradle correctly is more than adding one dependency. You need the right plugin versions, the right configuration flags, and the right test source layout so tests are discovered, executed, and reported correctly every time. This guide covers every configuration option from a basic single-module project to advanced multi-module and CI-ready setups.

If you are brand new to JUnit 6, start with Getting Started with JUnit 6 first. This post goes deeper into build tool specifics.

Part 1: JUnit 6 with Maven

Step 1 — The Core Dependency

Add junit-jupiter to your pom.xml. This single aggregator pulls in the API, params module, and engine in one shot:

<properties>
    <junit.version>6.1.1</junit.version>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
    <!-- JUnit Jupiter aggregator: API + Params + Engine -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>${junit.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Step 2 — Maven Surefire Plugin (Unit Tests)

The Maven Surefire Plugin runs unit tests during the test phase. You must use a 3.x version (3.2.5 or higher recommended) — JUnit 6 no longer supports Surefire/Failsafe older than 3.0.0:

<build>
    <plugins>
        <!-- Surefire: runs tests in the 'test' lifecycle phase -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.2.5</version>
            <configuration>
                <!-- Show test output in the console during build -->
                <useFile>false</useFile>
                <!-- Include these naming patterns for test discovery -->
                <includes>
                    <include>**/*Test.java</include>
                    <include>**/*Tests.java</include>
                    <include>**/Test*.java</include>
                </includes>
            </configuration>
        </plugin>
    </plugins>
</build>

Step 3 — Maven Failsafe Plugin (Integration Tests)

For integration tests (tests that start a server, connect to a database, etc.), use the Failsafe Plugin. By convention, integration test classes end with IT:

<!-- Failsafe: runs integration tests in 'verify' phase -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>3.2.5</version>
    <executions>
        <execution>
            <goals>
                <!-- 'integration-test' runs the tests -->
                <goal>integration-test</goal>
                <!-- 'verify' checks the results and fails the build if needed -->
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <!-- Integration tests match *IT.java by convention -->
        <includes>
            <include>**/*IT.java</include>
        </includes>
    </configuration>
</plugin>

Run integration tests with: mvn verify

Step 4 — Running Tests from Maven CLI

# Run all unit tests
mvn test

# Run a specific test class
mvn test -Dtest=CalculatorTest

# Run a specific test method
mvn test -Dtest=CalculatorTest#addingTwoPositiveNumbers

# Skip tests entirely (not recommended in CI)
mvn package -DskipTests

# Run integration tests
mvn verify

# Run only tests tagged with "fast"
mvn test -Dgroups=fast

# Exclude tests tagged with "slow"
mvn test -DexcludedGroups=slow

Complete Maven pom.xml Example

<?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-maven-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <junit.version>6.1.1</junit.version>
        <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>${junit.version}</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>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>3.2.5</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

Part 2: JUnit 6 with Gradle

Groovy DSL (build.gradle)

// build.gradle — Groovy DSL
plugins {
    id 'java'
}

group = 'com.example'
version = '1.0-SNAPSHOT'
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
    mavenCentral()
}

dependencies {
    // JUnit Jupiter aggregator (API + Params + Engine)
    testImplementation 'org.junit.jupiter:junit-jupiter:6.1.1'
}

test {
    // REQUIRED: activates the JUnit Platform test engine
    useJUnitPlatform()

    // Show individual test results in the console
    testLogging {
        events 'passed', 'failed', 'skipped'
        showStandardStreams = true
    }

    // Fail the build if no tests ran (catches misconfigured projects)
    failIfNoSpecifiedTests = false
}

Kotlin DSL (build.gradle.kts)

// build.gradle.kts — Kotlin DSL
plugins {
    java
}

group = "com.example"
version = "1.0-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
    mavenCentral()
}

dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter:6.0.0")
}

tasks.test {
    // REQUIRED: activates the JUnit Platform test engine
    useJUnitPlatform()

    testLogging {
        events("passed", "failed", "skipped")
    }
}

Running Tests from Gradle CLI

# Run all tests
./gradlew test

# Run a specific test class
./gradlew test --tests "com.example.CalculatorTest"

# Run a specific test method
./gradlew test --tests "com.example.CalculatorTest.addingTwoPositiveNumbers"

# Run only tests tagged with "fast"
./gradlew test -Pgroups=fast

# Re-run tests even if nothing changed (disables caching)
./gradlew test --rerun-tasks

# Show test output continuously (useful in CI)
./gradlew test --info

Part 3: Multi-Module Project Setup

In a multi-module Maven project, define JUnit 6 in the parent POM’s dependencyManagement section so child modules inherit the version automatically:

<!-- parent/pom.xml -->
<dependencyManagement>
    <dependencies>
        <!-- BOM import: manages all JUnit 6 module versions together -->
        <dependency>
            <groupId>org.junit</groupId>
            <artifactId>junit-bom</artifactId>
            <version>6.1.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<!-- child/pom.xml — no version needed, inherited from BOM -->
<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Part 4: Filtering Tests by Tag

JUnit 6 lets you tag tests with @Tag and then include or exclude them at build time. This is extremely useful for separating fast unit tests from slow integration tests.

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

class OrderServiceTest {

    // Tag this test as 'fast' — runs in every build
    @Test
    @Tag("fast")
    void creatingOrderSetsStatusToPending() {
        // fast, no I/O
    }

    // Tag this test as 'slow' — only runs when explicitly requested
    @Test
    @Tag("slow")
    void sendingOrderConfirmationEmailDelivered() {
        // slow, hits email server
    }
}

Configure Maven Surefire to only run fast tests during normal builds:

<configuration>
    <!-- Only run tests tagged 'fast' -->
    <groups>fast</groups>
    <!-- Or exclude tests tagged 'slow' -->
    <!-- <excludedGroups>slow</excludedGroups> -->
</configuration>

Part 5: IDE Integration

IntelliJ IDEA

IntelliJ IDEA has native JUnit 6 support. After adding the Maven/Gradle dependency:

  • Click the green â–¶ icon next to any @Test method to run it individually
  • Right-click the test class → Run All Tests
  • Use Run → Edit Configurations to create a reusable run config with tag filters
  • The test results panel shows a full tree view with pass/fail/skip indicators

Eclipse

Eclipse supports JUnit 6 via the built-in JUnit plug-in. Right-click the project or test class → Run As → JUnit Test. For Maven projects, ensure m2e (Maven Integration for Eclipse) is installed.

VS Code

Install the Extension Pack for Java which includes the Test Runner for Java. Tests appear in the Testing panel (beaker icon) with run/debug controls per method. Gradle and Maven projects are both supported automatically.

Part 6: Common Configuration Mistakes

MistakeSymptomFix
Surefire plugin version < 3.0.00 tests run / engine errorUpgrade to 3.2.5+
Missing useJUnitPlatform() in Gradle0 tests runAdd it to the test block
Test class is public but method is privateTest not discoveredUse package-private or public methods
Using junit-vintage-engine without junit-jupiterJUnit 4 tests run, JUnit 6 tests don’tAdd both engines if needed
Dependency scope is compile instead of testJUnit ends up in production JARUse scope=test / testImplementation

Expected Console Output

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.example.CalculatorTest
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.41 s

Results:
Tests run: 6, Failures: 0, Errors: 0, Skipped: 0

BUILD SUCCESS

Frequently Asked Questions (FAQs)

Q1: What is the difference between maven-surefire-plugin and maven-failsafe-plugin?

Surefire runs unit tests during the test phase and fails the build immediately if a test fails. Failsafe runs integration tests during the integration-test phase and defers build failure to the verify phase — this allows teardown steps (like stopping a test server) to run even if a test fails.

Q2: Why do I need useJUnitPlatform() in Gradle?

Without useJUnitPlatform(), Gradle uses its own legacy test discovery mechanism which does not know about JUnit Platform engines. This results in zero tests being discovered or run even though your test classes exist. This line activates the JUnit 6 engine for test discovery and execution.

Q3: Can I use both JUnit 4 and JUnit 6 tests in the same Maven project?

Yes. Add both junit-jupiter (for JUnit 6 tests) and junit-vintage-engine (to run old JUnit 4 tests on the JUnit Platform). Both will be discovered and run by Surefire automatically during the same build.

Q4: How do I generate an HTML test report with Maven?

Maven Surefire generates XML reports in target/surefire-reports/ automatically. To get an HTML report, run mvn surefire-report:report after your tests. The HTML report is generated in target/site/surefire-report.html. For richer reports, integrate Allure — covered in Test Reporting in JUnit 6.

Q5: How do I run JUnit 6 tests in a multi-module Gradle project?

Add a subprojects block in your root build.gradle to apply the JUnit 6 configuration to every submodule:

// root build.gradle
subprojects {
    apply plugin: 'java'
    dependencies {
        testImplementation 'org.junit.jupiter:junit-jupiter:6.1.1'
    }
    test {
        useJUnitPlatform()
    }
}

See Also

Conclusion

You now have a complete, production-ready JUnit 6 build configuration for both Maven and Gradle. You know how to separate unit tests from integration tests, filter by tag, handle multi-module projects, and avoid the most common setup mistakes.

Next: JUnit 6 Project Structure and Best Practices — learn how to organise your test source tree for long-term maintainability.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.