What are AssertJ soft assertions, when do you use them, and what is the gotcha that makes failures disappear?
answer
- Hard = first failure stops; soft = collect all, report together
- softly.assertThat(...) then softly.assertAll()
- Forgetting assertAll() -> failures swallowed, test passes green
- assertSoftly(softly -> {...}) / @InjectSoftAssertions call assertAll for you
- Use for independent properties; not when later checks depend on earlier
basics
~20 sNormally the first failed assertion throws and stops the test, hiding later problems. Soft assertions collect all failures and report them together. You make assertions through a SoftAssertions object, then call assertAll() at the end. The gotcha: if you forget assertAll(), every failure is silently swallowed and the test passes.
solid answer
~50 sBy default AssertJ assertions are 'hard': the first failure throws AssertionError and the rest of the test never runs, so you fix one problem, rerun, find the next. Soft assertions let you gather multiple failures in one run and see them all together, which is valuable when verifying several independent properties of one result. You create a SoftAssertions instance, call softly.assertThat(...) for each check, then softly.assertAll() to throw a combined error listing every failure. JUnit 5 offers assertSoftly(softly -> {...}) and a @ExtendWith(SoftAssertionsExtension.class) with an injected SoftAssertions/@InjectSoftAssertions field that calls assertAll automatically after the test. The critical gotcha: with the manual SoftAssertions you must call assertAll(); if you forget it, all collected failures are discarded and the test passes green even though assertions failed. Prefer assertSoftly or the extension to make this impossible to forget.
code
java · 14 linesimport static org.assertj.core.api.SoftAssertions.assertSoftly;
import org.assertj.core.api.SoftAssertions;
// Manual form - MUST call assertAll() or failures vanish
SoftAssertions softly = new SoftAssertions();
softly.assertThat(user.getName()).isEqualTo("Ann");
softly.assertThat(user.getAge()).isEqualTo(30);
softly.assertAll(); // throws one combined error listing all failures
// Safer: assertAll() is automatic
assertSoftly(s -> {
s.assertThat(user.getName()).isEqualTo("Ann");
s.assertThat(user.getAge()).isEqualTo(30);
});go deeper
Knows hard assertions stop at the first failure and that soft assertions collect multiple failures.
Can write SoftAssertions with assertAll() and explain the report-all-failures benefit.
Uses assertSoftly or the JUnit 5 extension to avoid the forgotten-assertAll gotcha, and knows when hard assertions are safer (dependent checks).
Sets a team policy (e.g. mandate assertSoftly/extension, ban bare SoftAssertions) and may add a lint/static check to catch a missing assertAll() to prevent silent green failures.
## Hard assertions: fail-fast A normal AssertJ assertion is **hard**: when it fails it immediately throws `AssertionError`, aborting the test method. So if a result has three wrong fields, you see only the **first**, fix it, rerun, see the second, and so on. For a single result with several independent properties, this is a slow feedback loop. ## Soft assertions: collect-then-report **Soft assertions** record each failure instead of throwing, then report them **all at once**. You route assertions through a `SoftAssertions` object: ```java SoftAssertions softly = new SoftAssertions(); softly.assertThat(user.getName()).isEqualTo("Ann"); softly.assertThat(user.getAge()).isEqualTo(30); softly.assertThat(user.getEmail()).endsWith("@x.com"); softly.assertAll(); // throws ONE error listing every failure ``` The final `assertAll()` checks the collected list; if non-empty it throws a single `AssertionError` (technically a multiple-failures error) enumerating **all** failed checks with their messages. One run shows you every broken field. ## When to use them - Verifying **several independent properties** of one object/response (a DTO's fields, an HTTP response's status + headers + body) where seeing all failures speeds debugging. - **Not** when later assertions *depend* on earlier ones holding (e.g. you assert non-null then dereference) — there a hard assertion should stop first to avoid a misleading NPE. ## The dangerous gotcha: forgetting assertAll() With the **manual** `new SoftAssertions()`, the failures live only in that object. If you never call `assertAll()`, they are **silently swallowed** — the test method completes normally and reports **green** even though assertions failed. This is a classic source of false confidence and has shipped real bugs. ### Safer forms that call assertAll() for you 1. **`assertSoftly`** — a static helper that runs a lambda and calls `assertAll()` automatically at the end: ```java assertSoftly(softly -> { softly.assertThat(user.getName()).isEqualTo("Ann"); softly.assertThat(user.getAge()).isEqualTo(30); }); ``` 2. **JUnit 5 extension** — `@ExtendWith(SoftAssertionsExtension.class)` with an `@InjectSoftAssertions SoftAssertions softly;` field; the extension calls `assertAll()` after each test automatically. No manual call, so it cannot be forgotten. 3. **try-with-resources** — `AutoCloseableSoftAssertions` calls `assertAll()` in `close()`. ## Interaction with other features Soft assertions still support the full fluent API, including `extracting`, exception assertions, and chaining — every check just records instead of throwing. ## Summary trade-off Soft assertions trade fail-fast simplicity for **complete failure reporting**. The price is the assertAll discipline; prefer `assertSoftly` or the JUnit extension so the call is structural and cannot be omitted.
- What happens if you forget to call assertAll() with a manual SoftAssertions?All recorded failures are discarded and the test reports green even though assertions failed. The failures live only inside the SoftAssertions object and are never thrown. Using assertSoftly(...) or the @InjectSoftAssertions JUnit 5 extension prevents this because they call assertAll() for you.
- When should you prefer hard assertions over soft ones?When later assertions depend on earlier ones being true — e.g. you assert a value is non-null and then dereference it. A hard assertion stops at the first failure so you avoid a confusing secondary failure (like an NPE) that obscures the real cause.
saying these in an interview costs you the question
- Forgetting assertAll() and assuming the test still catches failures
- Using soft assertions where a later check dereferences a value an earlier check should have guarded
- Thinking soft assertions change the assertion semantics (they only defer throwing)
- Reusing one SoftAssertions across tests without re-creating/resetting it