Duplicate Lifecycle Method

ID

java.test_classes_contain_multiple_lifecycle

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Java

Tags

reliability, testing

Description

Reports a class declaring more than one method for the same lifecycle annotation — @Before, @After, @BeforeClass, @AfterClass in JUnit 4, or @BeforeEach, @AfterEach, @BeforeAll, @AfterAll in JUnit 5.

Rationale

JUnit does not guarantee the relative order in which two methods carrying the same lifecycle annotation run. A test suite can look and pass exactly as intended for a long time, because the current internal ordering happens to work, then start failing after an unrelated change or a JUnit upgrade changes that internal order — with no change to the test itself to point at.

class WarehouseInventoryTest {

  @BeforeEach
  void resetStock() {
    stock = 0;
  }

  @BeforeEach
  void primeSupplierMock() {
    // relies on stock already being reset — but that order is not guaranteed
  }
}

Remediation

Merge the duplicated lifecycle methods into one. If the original methods needed to run in a specific order, call them in that order from the merged method, so the order is explicit rather than incidental.

class WarehouseInventoryTest {

  @BeforeEach
  void setUp() {
    stock = 0;
    primeSupplierMock();
  }
}

What is not reported

The check is strictly per class. A lifecycle method declared in a superclass and one declared in a subclass never collide, because JUnit does guarantee that ordering across a class hierarchy: "before" methods run superclass-first, "after" methods run subclass-first. TestNG’s own lifecycle annotations are out of scope, since this defect is specific to JUnit 4 and JUnit 5.

Cucumber’s io.cucumber.java.Before / io.cucumber.java.After hooks are out of scope for the same reason as TestNG’s: they share a simple name with the JUnit 4 annotations but take an explicit order attribute, so two of them in one class state their relative order rather than leaving it unspecified.

class MessagingHook { // not reported: the order is declared, not incidental

  @Before(order = 0)
  public void waitForListenerStartup() { }

  @Before(order = 1)
  public void emptyEventStore() { }
}

© 2026 Xygeni. All rights reserved