A hard assertion stops the test immediately on failure; a soft assertion records the failure but continues, reporting all failures at the end. TestNG has a built-in SoftAssert; JUnit 5 achieves 'soft' behavior with assertAll.
Standard Assert.assertEquals(...) (TestNG) / assertEquals(...) (JUnit) are HARD — the first failure throws and the rest of the test is skipped. This is right when later steps depend on the assertion. SOFT assertions let you verify multiple independent things and see ALL failures in one run: TestNG's SoftAssert softAssert.assertEquals(...); ... softAssert.assertAll(); collects failures and throws at assertAll(). JUnit 5's assertAll(() -> assertEquals(...), () -> assertTrue(...)) runs all lambdas and aggregates failures. Use soft assertions for validating many fields of a response/page in one test; use hard assertions when a failure invalidates subsequent steps.
Validating a user profile page: a SoftAssert checks name, email, role, and status; if two are wrong, the test reports BOTH in one run instead of failing at the first — faster debugging than fixing one at a time.
You verify 5 independent fields of an API response in one test. With hard asserts you only ever see the first failure. What's the fix?