JUnit 5 Annotations — Complete Reference

3 minute read

JUnit 5 provides rich annotations to control test lifecycle, naming, ordering, and execution. This chapter covers every annotation you’ll use daily.


Lifecycle Annotations

import org.junit.jupiter.api.*;

class LifecycleTest {

    @BeforeAll
    static void initAll() {
        System.out.println("Once before ALL tests (static)");
        // Database connection, test container startup, etc.
    }

    @BeforeEach
    void init() {
        System.out.println("Before EACH test");
        // Reset state, create fresh objects
    }

    @Test
    void testOne() {
        System.out.println("Test 1");
    }

    @Test
    void testTwo() {
        System.out.println("Test 2");
    }

    @AfterEach
    void tearDown() {
        System.out.println("After EACH test");
        // Cleanup per-test resources
    }

    @AfterAll
    static void tearDownAll() {
        System.out.println("Once after ALL tests (static)");
        // Close connections, stop containers
    }
}

Output:

Once before ALL tests
Before EACH test → Test 1 → After EACH test
Before EACH test → Test 2 → After EACH test
Once after ALL tests
Annotation Runs Must Be Static?
@BeforeAll Once before all tests Yes*
@BeforeEach Before each test method No
@AfterEach After each test method No
@AfterAll Once after all tests Yes*

*Unless using @TestInstance(Lifecycle.PER_CLASS).


@DisplayName — Human-Readable Test Names

@DisplayName("User Registration Service")
class UserRegistrationTest {

    @Test
    @DisplayName("should create user when email is valid")
    void shouldCreateUserWhenEmailIsValid() {
        // ...
    }

    @Test
    @DisplayName("should throw exception when email is blank")
    void shouldThrowWhenEmailBlank() {
        // ...
    }
}

IDE output:

User Registration Service
  ✓ should create user when email is valid
  ✓ should throw exception when email is blank

@Disabled — Skip Tests

@Test
@Disabled("Bug #1234 — waiting for API fix")
void shouldProcessPayment() {
    // This test won't run
}

@Disabled("Migration in progress")
class LegacyServiceTest {
    // Entire class skipped
}

Conditional Disabling

@Test
@EnabledOnOs(OS.LINUX)
void onlyOnLinux() {}

@Test
@EnabledOnJre(JRE.JAVA_21)
void onlyOnJava21() {}

@Test
@EnabledIfEnvironmentVariable(named = "CI", matches = "true")
void onlyInCI() {}

@Test
@EnabledIfSystemProperty(named = "os.arch", matches = "aarch64")
void onlyOnArm() {}

@Tag — Categorize Tests

@Tag("fast")
@Test
void shouldValidateEmail() {}

@Tag("slow")
@Tag("integration")
@Test
void shouldQueryDatabase() {}

Run only tagged tests with Maven:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <groups>fast</groups>           <!-- include -->
        <excludedGroups>slow</excludedGroups>  <!-- exclude -->
    </configuration>
</plugin>
mvn test -Dgroups=fast

@Timeout — Fail Slow Tests

@Test
@Timeout(5)  // fail if takes longer than 5 seconds
void shouldRespondQuickly() {
    // ...
}

@Test
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
void shouldBeVeryFast() {
    // ...
}

Class-Level Timeout

@Timeout(10)
class PerformanceTest {
    // All tests in this class must complete within 10 seconds each
}

@Order and @TestMethodOrder

By default, test execution order is not guaranteed. To enforce order:

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrderedTest {

    @Test
    @Order(1)
    void createUser() {
        System.out.println("Step 1: Create user");
    }

    @Test
    @Order(2)
    void verifyUser() {
        System.out.println("Step 2: Verify user");
    }

    @Test
    @Order(3)
    void deleteUser() {
        System.out.println("Step 3: Delete user");
    }
}

Other Orderers

@TestMethodOrder(MethodOrderer.DisplayName.class)      // alphabetical by display name
@TestMethodOrder(MethodOrderer.MethodName.class)        // alphabetical by method name
@TestMethodOrder(MethodOrderer.Random.class)            // random (good for finding dependencies)

Warning: Ordered tests often indicate a design smell. Tests should be independent. Use ordering only for integration test scenarios.


@TestInstance — Lifecycle Per Class

By default, JUnit creates a new instance per test method. You can change this:

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SharedStateTest {

    private final List<String> items = new ArrayList<>();

    @BeforeAll
    void setUp() {  // no longer needs to be static!
        items.add("initial");
    }

    @Test
    void testA() {
        items.add("A");
        assertEquals(2, items.size());
    }

    @Test
    void testB() {
        // ⚠️ items may contain "A" if testA ran first!
        // This is why PER_METHOD (default) is safer
    }
}

Use PER_CLASS when @BeforeAll/@AfterAll need instance methods (non-static), or with @Testcontainers.


Annotation Summary

Annotation Purpose
@Test Marks a test method
@BeforeEach / @AfterEach Per-test setup/teardown
@BeforeAll / @AfterAll Per-class setup/teardown
@DisplayName Human-readable name
@Disabled Skip test/class
@Tag Categorize (fast, slow, integration)
@Timeout Fail if too slow
@Order Execution order
@TestMethodOrder Ordering strategy
@TestInstance Lifecycle: per-method vs per-class
@RepeatedTest Run N times
@ParameterizedTest Run with different inputs

Common Mistakes

Mistake Fix
@BeforeAll method not static Make it static or use @TestInstance(PER_CLASS)
Relying on test order Tests should be independent — use @BeforeEach for setup
@Disabled without reason Always add a reason string
@Tag with spaces Use simple strings: "fast", "integration"

Key Takeaways

  1. Use @BeforeEach for per-test setup, @BeforeAll for expensive one-time setup.
  2. @DisplayName makes test output readable for non-developers.
  3. @Tag lets you run subsets of tests (fast in dev, all in CI).
  4. @Timeout catches performance regressions.
  5. Tests should be independent — avoid relying on @Order.

What’s Next?

Next: Assertions in JUnit 5 — assertEquals, assertThrows, assertAll, and fluent assertions with AssertJ.

Updated: