If you came through the TestNG course, you already know what a test framework does: it discovers tests, runs lifecycle methods in the right order, feeds parameterised data, and produces a report. JUnit 5 does all of the same things — but it does them through a layered architecture that no previous Java test framework had. Understanding that architecture takes ten minutes and saves hours of confusion later, because every error message, every Maven configuration line, and every extension you'll write maps directly onto one of those layers.
JUnit 5 is three things, not one
Every other framework you've used — JUnit 4, TestNG — ships as a single jar. JUnit 5 ships as three separate modules with clear contracts between them:
JUnit Platform is the foundation. It defines the TestEngine interface and the Launcher API that build tools, IDEs, and CI servers call to discover and execute tests. Maven's Surefire plugin, IntelliJ's test runner, and GitHub Actions all talk to the Platform — none of them know anything about Jupiter or TestNG directly. They ask the Platform to run the tests; the Platform delegates to the engines.
JUnit Jupiter is the new programming model. This is the module you write code against: @Test, @BeforeEach, @AfterEach, @ParameterizedTest, @ExtendWith, and the Assertions class. Jupiter is itself a TestEngine that registers with the Platform and handles all Jupiter-annotated tests.
JUnit Vintage is the backward-compatibility bridge. It is another TestEngine that runs JUnit 3 and JUnit 4 tests on the JUnit 5 Platform. When you add junit-vintage-engine to a project that already has JUnit 4 tests, those old tests run unchanged — no migration needed. Vintage is not for new projects; it is a migration aid.
Why the split matters
The Platform layer is why the same mvn test command can run Jupiter tests, Vintage tests, and even Cucumber scenarios in the same build — each has its own engine registered with the Platform. It also means tool vendors implement Platform support once and instantly gain the ability to run any engine that plugs in. IntelliJ didn't have to rewrite its test runner for JUnit 5; it just updated its Platform client.
JUnit 5 vs JUnit 4
JUnit 4 was the dominant Java test framework for fifteen years. JUnit 5 replaced almost every annotation and added capabilities JUnit 4 never had:
| JUnit 4 | JUnit 5 (Jupiter) |
|---|---|
@Before | @BeforeEach |
@After | @AfterEach |
@BeforeClass (static) | @BeforeAll (static by default) |
@AfterClass (static) | @AfterAll (static by default) |
@Ignore | @Disabled |
@Rule / @ClassRule | @ExtendWith (Extension model) |
@RunWith | @ExtendWith |
@Category | @Tag |
| No nested tests | @Nested |
| Limited parameterised support | @ParameterizedTest built in |
expected = Exception.class on @Test | assertThrows(...) |
The package also changed entirely. JUnit 4 lives in org.junit. Jupiter lives in org.junit.jupiter.api. If you see import org.junit.Test in a file, that is JUnit 4. Jupiter imports start with org.junit.jupiter.
JUnit 5 vs TestNG
You spent a whole course on TestNG. Here is where they differ in practice:
TestNG still has an edge on: XML suite files for complex multi-test-block configuration, @DataProvider with @Factory combinations, and the mature ITestListener / IReporter ecosystem for Selenium reporting.
JUnit 5 has an edge on: The @ExtendWith Extension model (far more composable than TestNG Listeners), @Nested test classes for grouping scenarios, assertAll for non-short-circuiting assertions, and tighter Spring Boot integration.
When to choose which: JUnit 5 is the industry default for new Java projects and unit testing. TestNG still dominates large, existing Selenium frameworks — teams invest in it and don't switch without reason. Both are production-grade; the real criterion is what the project already uses and what job descriptions in your area ask for. This course focuses on JUnit 5 because it is where new projects land.
Architecture at a glance
- – Launcher API for tools
- – TestEngine interface
- – Maven, IntelliJ, CI connect here
- – @Test, @BeforeEach, @AfterEach
- – @ParameterizedTest, @ExtendWith
- – Assertions, Assumptions
- Runs JUnit 3 and 4 tests –
- Migration aid — not for new code –
- Registers as a Platform engine –
⚠️ Common mistakes
- Importing from
org.junitinstead oforg.junit.jupiter.api. The JUnit 4 jar and the Jupiter jar can coexist on the classpath during migration. IntelliJ sometimes suggestsorg.junit.Testwhen you meanorg.junit.jupiter.api.Test. Always check the import — the wrong one silently compiles, but the test may not behave as a Jupiter test. - Confusing Vintage with Jupiter. Vintage does not give you JUnit 5 features. Adding
junit-vintage-engineto a new project and expecting@Nestedor@ParameterizedTestto work is backwards — those are Jupiter features. Vintage only adds the ability to run old JUnit 4 tests through the new Platform. - Treating "JUnit 5" and "JUnit Jupiter" as synonyms. Technically, "JUnit 5" names the whole project (Platform + Jupiter + Vintage). "Jupiter" names the programming model module specifically. Most people say "JUnit 5" and mean "Jupiter" — which is fine in conversation, but matters when you're reading Maven dependency names.
🎯 Practice task
Get oriented before writing a line of code. 10–15 minutes.
- Open the JUnit 5 User Guide and navigate to the Architecture section. Read the first three paragraphs. Notice the language: Platform, Jupiter, Vintage — not "JUnit 5" as a monolith.
- Open a project that uses TestNG (your Selenium project, if you did that course). Find one
import org.testng.annotations.Testand oneimport org.testng.Assert. These are the TestNG equivalents of what you'll import from Jupiter. Keep this side-by-side reference in mind as you progress through the course. - In a new text file, write the Jupiter imports for the four lifecycle annotations and the Assertions class. Do not look them up — reason from the pattern
org.junit.jupiter.api.*and the annotation names from the comparison table above. Then verify against the next lesson.
Next lesson: setting up a JUnit 5 Maven project from scratch with the correct Surefire configuration.