If you are learning modern Java testing, the words JUnit Platform, JUnit Jupiter, and JUnit Vintage can be confusing at first.
They sound like three different testing frameworks, but they are really three parts of the JUnit 5 ecosystem.
The short version is:
JUnit Platform runs tests.
JUnit Jupiter is the modern JUnit 5 programming model.
JUnit Vintage lets old JUnit 3 and JUnit 4 tests run on the JUnit 5 Platform.
Let’s break that down clearly.
The Big Picture
JUnit 5 is not one single library in the same way JUnit 4 was. It is an ecosystem made of several modules.
The three most important names are:
| Name | Purpose |
|---|---|
| JUnit Platform | Foundation for discovering and running tests |
| JUnit Jupiter | API and engine for writing and running JUnit 5 tests |
| JUnit Vintage | Engine for running legacy JUnit 3 and JUnit 4 tests |
A useful mental model is:
JUnit 5
├── JUnit Platform
│ ├── Test discovery
│ ├── Test execution
│ └── Integration with IDEs, Maven, Gradle, etc.
│
├── JUnit Jupiter
│ ├── @Test
│ ├── @BeforeEach
│ ├── @AfterEach
│ ├── @ParameterizedTest
│ └── Modern JUnit 5 test engine
│
└── JUnit Vintage
└── Runs old JUnit 3 / JUnit 4 tests
1. What Is the JUnit Platform?
The JUnit Platform is the foundation of JUnit 5.
It is responsible for:
- discovering tests
- launching tests
- reporting results
- providing an API for tools such as:
- IntelliJ IDEA
- Eclipse
- Maven Surefire
- Gradle
- build servers
- custom test runners
The Platform itself does not define the main annotations you usually write in your test classes.
For example, this annotation:
@Test
it usually comes from JUnit Jupiter, not from the Platform.
The Platform answers the question:
How do tools find and run tests?
It does not primarily answer:
How do I write a test method?
That job belongs to Jupiter.
JUnit Platform as a Test Launcher
When you click Run Test in your IDE, the IDE does not need to understand every testing framework in detail.
Instead, it can use the JUnit Platform to discover and execute tests.
The Platform can run tests through different test engines.
A test engine is an implementation that knows how to discover and execute a specific kind of test.
For example:
| Test Engine | Runs |
|---|---|
| Jupiter Engine | JUnit 5/Jupiter tests |
| Vintage Engine | JUnit 3 and JUnit 4 tests |
| Other engines | Other testing frameworks that integrate with the Platform |
So the Platform is like a central test-running infrastructure.
2. What Is JUnit Jupiter?
JUnit Jupiter is what most people mean when they say “JUnit 5 tests.”
It includes two main things:
- JUnit Jupiter API
- JUnit Jupiter Engine
The API gives you annotations and assertions for writing tests.
The engine allows those tests to be discovered and executed by the JUnit Platform.
JUnit Jupiter API
When you write a modern JUnit 5 test, you usually import from:
org.junit.jupiter.api
Example:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@Test
void addsTwoNumbers() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result);
}
}
This is a JUnit Jupiter test.
The annotation is:
org.junit.jupiter.api.Test
not:
org.junit.Test
That distinction matters.
Common Jupiter Annotations
JUnit Jupiter provides the modern test annotations:
| Annotation | Purpose |
|---|---|
@Test |
Marks a test method |
@BeforeEach |
Runs before each test |
@AfterEach |
Runs after each test |
@BeforeAll |
Runs once before all tests |
@AfterAll |
Runs once after all tests |
@DisplayName |
Gives a readable test name |
@Nested |
Creates nested test classes |
@Disabled |
Disables a test |
@ParameterizedTest |
Runs the same test with different inputs |
Example:
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class StringFormatterTest {
private StringFormatter formatter;
@BeforeEach
void setUp() {
formatter = new StringFormatter();
}
@Test
@DisplayName("Converts text to uppercase")
void convertsTextToUppercase() {
assertEquals("HELLO", formatter.uppercase("hello"));
}
}
JUnit Jupiter Engine
Writing the test is only half of the story.
To run Jupiter tests, the JUnit Platform needs the Jupiter Engine.
In a Maven project, you commonly add:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
The junit-jupiter dependency is a convenient aggregate dependency that brings in the Jupiter API and engine.
With Gradle:
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'
}
test {
useJUnitPlatform()
}
In modern Gradle Kotlin DSL:
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.13.4")
}
tasks.test {
useJUnitPlatform()
}
The important idea is:
Jupiter gives you the annotations and behavior for modern JUnit 5 tests, while the Platform provides the infrastructure to run them.
3. What Is JUnit Vintage?
JUnit Vintage exists for backward compatibility.
It lets you run old JUnit 3 and JUnit 4 tests on the JUnit Platform.
This is useful when a project is migrating from JUnit 4 to JUnit 5.
Suppose you have an old JUnit 4 test:
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class LegacyCalculatorTest {
@Test
public void addsTwoNumbers() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result);
}
}
Notice the imports:
import org.junit.Test;
import static org.junit.Assert.assertEquals;
That is JUnit 4 style.
A Jupiter test would use:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
If your project includes the Vintage Engine, the JUnit Platform can discover and run that old JUnit 4 test.
Vintage Is for Running Old Tests, Not Writing New Ones
You generally should not write new tests using Vintage.
For new tests, prefer Jupiter.
Use Vintage only when:
- you already have JUnit 3 or JUnit 4 tests
- you want to migrate gradually
- you need old tests to keep running during the migration
In Maven, Vintage usually looks like this:
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
In Gradle:
dependencies {
testImplementation 'org.junit.vintage:junit-vintage-engine:5.13.4'
}
test {
useJUnitPlatform()
}
Platform vs. Jupiter vs. Vintage
Here is the clearest comparison:
| Component | What it is | You use it for |
|---|---|---|
| JUnit Platform | Test launching infrastructure | Running tests through IDEs/build tools |
| JUnit Jupiter | Modern JUnit 5 programming and extension model | Writing new JUnit 5 tests |
| JUnit Vintage | Legacy test engine | Running old JUnit 3/JUnit 4 tests |
Another way to say it:
JUnit Platform = test runtime infrastructure
JUnit Jupiter = modern JUnit 5 tests
JUnit Vintage = compatibility layer for old tests
Why Did JUnit 5 Split Things This Way?
JUnit 4 was more monolithic. JUnit 5 was redesigned to be more modular.
This design allows the JUnit Platform to run different kinds of tests through different engines.
For example, the Platform can support:
- JUnit Jupiter tests
- JUnit Vintage tests
- third-party test engines
- custom test frameworks
This makes JUnit 5 more flexible than older versions.
The key design idea is:
The Platform does not care what kind of test you wrote, as long as there is a test engine that knows how to run it.
A Practical Example
Imagine your project has both new and old tests.
src/test/java
├── CalculatorJupiterTest.java
└── LegacyCalculatorJUnit4Test.java
The new test:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorJupiterTest {
@Test
void addsTwoNumbers() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3));
}
}
The old test:
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class LegacyCalculatorJUnit4Test {
@Test
public void addsTwoNumbers() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3));
}
}
To run both on the JUnit Platform, you need:
- JUnit Jupiter Engine for the Jupiter test
- JUnit Vintage Engine for the JUnit 4 test
Maven example:
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
</dependencies>
Now the JUnit Platform can run both types of tests.
Common Import Mistake
One of the most common mistakes during migration is mixing JUnit 4 and JUnit Jupiter imports.
For JUnit Jupiter, use:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
For JUnit 4, use:
import org.junit.Test;
import static org.junit.Assert.assertEquals;
Do not accidentally write a Jupiter-style test with JUnit 4 imports.
For example, this is suspicious:
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
public class MixedTest {
@BeforeEach
void setUp() {
}
@Test
public void testSomething() {
}
}
Here, @BeforeEach is from Jupiter, but @Test is from JUnit 4.
That can lead to confusing behavior.
A clean Jupiter version would be:
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class CleanJupiterTest {
@BeforeEach
void setUp() {
}
@Test
void testSomething() {
}
}
Do I Always Need All Three?
No.
For a new Java project, you usually need:
JUnit Platform + JUnit Jupiter
You usually do not need Vintage.
For a project with old JUnit 4 tests, you may need:
JUnit Platform + JUnit Jupiter + JUnit Vintage
For a project that only uses JUnit 4 and has not moved to JUnit 5 infrastructure, you may not be using the JUnit Platform at all.
What Should I Use Today?
For new tests, use JUnit Jupiter.
A typical modern test should look like this:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
class UserServiceTest {
@Test
void createsActiveUser() {
UserService userService = new UserService();
User user = userService.createUser("[email protected]");
assertTrue(user.isActive());
}
}
Recommended approach:
| Situation | Recommendation |
|---|---|
| Starting a new project | Use JUnit Jupiter |
| Maintaining JUnit 4 tests | Add Vintage temporarily |
| Migrating to JUnit 5 | Run old tests with Vintage, write new tests with Jupiter |
| Finished migration | Remove Vintage |
Simple Analogy
Think of a theater.
| JUnit Part | Theater Analogy |
|---|---|
| JUnit Platform | The theater building, stage, lights, and ticket system |
| JUnit Jupiter | A modern play performed on the stage |
| JUnit Vintage | An old classic play adapted to run on the same stage |
The Platform provides the place and infrastructure.
Jupiter and Vintage provide different kinds of performances.
Final Summary
JUnit 5 is made of multiple parts:
- JUnit Platform is the foundation that discovers and runs tests.
- JUnit Jupiter is the modern JUnit 5 API and engine used for writing new tests.
- JUnit Vintage is the compatibility engine for running JUnit 3 and JUnit 4 tests.
If you are writing new Java tests today, focus on JUnit Jupiter.
If your project still has older JUnit 4 tests, use JUnit Vintage temporarily so those tests can continue running on the JUnit Platform while you migrate.
