JUnit 5 Architecture — Jupiter, Vintage, Platform

8 min read

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 4JUnit 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 @TestassertThrows(...)

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

JUnit 5
  • – 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.junit instead of org.junit.jupiter.api. The JUnit 4 jar and the Jupiter jar can coexist on the classpath during migration. IntelliJ sometimes suggests org.junit.Test when you mean org.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-engine to a new project and expecting @Nested or @ParameterizedTest to 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.

  1. 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.
  2. Open a project that uses TestNG (your Selenium project, if you did that course). Find one import org.testng.annotations.Test and one import 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.
  3. 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.

// tip to track lessons you complete and pick up where you left off across devices.