How do you assert that code throws an exception in AssertJ using assertThatThrownBy and assertThatExceptionOfType?
answer
- Wrap throwing code in a lambda () -> ...
- assertThatThrownBy(...).isInstanceOf(...).hasMessageContaining(...)
- assertThatExceptionOfType(X).isThrownBy(...).withMessage...
- Fails automatically if nothing is thrown (no forgotten fail())
- isInstanceOf allows subclasses; isExactlyInstanceOf does not
- assertThatCode(...).doesNotThrowAnyException() for the no-throw case
basics
~10 sWrap the failing call in a lambda: assertThatThrownBy(() -> service.run()).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("bad"). It runs the lambda, catches the thrown exception, and lets you assert on its type and message. assertThatExceptionOfType(X.class).isThrownBy(() -> ...) is an equivalent style.
solid answer
~40 sAssertJ captures the exception so you can assert on it fluently. assertThatThrownBy(throwingCallable) executes the lambda, expects it to throw, and returns a Throwable assertion: chain isInstanceOf(SomeException.class), hasMessage / hasMessageContaining / hasMessageMatching, hasCauseInstanceOf, and hasNoCause. assertThatExceptionOfType(SomeException.class).isThrownBy(() -> ...) reads more declaratively and pins the type up front, returning an assertion you refine with withMessage/withMessageContaining. For specific types there are shortcuts like assertThatIllegalArgumentException(), assertThatNullPointerException(), and assertThatIOException(). A key correctness point: the throwing code must be inside the lambda so AssertJ controls execution; if it throws outside, the test errors instead of asserting. assertThatCode(() -> ...).doesNotThrowAnyException() asserts the opposite. These are more fluent than try/catch/fail and than JUnit 5's assertThrows, while interoperating with it.
code
java · 18 linesimport static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatCode;
// style 1: throwing call inside the lambda
assertThatThrownBy(() -> service.run(badInput))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("bad")
.hasNoCause();
// style 2: type-first, declarative
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> service.close())
.withMessageContaining("already closed");
// assert it does NOT throw
assertThatCode(() -> service.run(goodInput))
.doesNotThrowAnyException();go deeper
Can write assertThatThrownBy(() -> ...).isInstanceOf(X.class) and check the message.
Knows both assertThatThrownBy and assertThatExceptionOfType styles, the type shortcuts, hasCause/hasMessageContaining, and why the call goes inside the lambda.
Chooses isInstanceOf vs isExactlyInstanceOf intentionally, asserts on cause chains, uses doesNotThrowAnyException, and relates AssertJ to JUnit 5 assertThrows.
Standardizes exception-assertion style across the suite, ensures negative paths are tested without false passes, and guides on testing exception contracts and messages without over-asserting brittle text.
## The problem: asserting on a thrown exception Sometimes the *correct* behavior is to **throw**. You must (1) run the code, (2) confirm it threw, (3) confirm it threw the **right type**, and often (4) confirm the **message** or **cause**. The naive approach is verbose and bug-prone: ```java try { service.run(badInput); fail("expected exception"); // easy to forget -> false pass } catch (IllegalArgumentException e) { assertThat(e).hasMessageContaining("bad"); } ``` Forgetting the `fail(...)` line makes the test pass even when nothing throws. ## AssertJ's solution: capture via a lambda The throwing code is wrapped in a **`ThrowableAssert.ThrowingCallable`** — a functional interface, so you pass a lambda. AssertJ runs it, catches whatever it throws, and hands you a **Throwable assertion**. ### Style 1 — `assertThatThrownBy` ```java assertThatThrownBy(() -> service.run(badInput)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("bad") .hasNoCause(); ``` `assertThatThrownBy` **also fails if nothing is thrown**, eliminating the forgotten-`fail` bug. Useful chained checks: `isInstanceOf` / `isExactlyInstanceOf`, `hasMessage` (exact), `hasMessageContaining`, `hasMessageStartingWith`, `hasMessageMatching` (regex), `hasCauseInstanceOf(...)`, `getCause()`, `hasCause(...)`, `hasNoCause()`. ### Style 2 — `assertThatExceptionOfType` ```java assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> service.run(badInput)) .withMessageContaining("bad") .withCauseInstanceOf(IllegalStateException.class); ``` This pins the expected type **first**, then `isThrownBy` runs the lambda; refinements use the `with...` prefix. It reads as a declarative sentence and fails if the type does not match or nothing throws. ### Style 3 — type shortcuts For common JDK exceptions: `assertThatIllegalArgumentException()`, `assertThatNullPointerException()`, `assertThatIllegalStateException()`, `assertThatIOException()`, each followed by `.isThrownBy(() -> ...)`. ### Asserting NO exception ```java assertThatCode(() -> service.run(goodInput)) .doesNotThrowAnyException(); ``` ## The critical correctness rule The throwing call **must be inside the lambda**. If you write `service.run(badInput)` directly (not in a lambda), it throws while building the test, so the exception escapes the assertion and the test **errors** rather than asserting — and the AssertJ assertion never runs. Always wrap the *action that may throw* in the `() -> ...`. ## isInstanceOf vs isExactlyInstanceOf `isInstanceOf(IllegalArgumentException.class)` also passes for subclasses (e.g. `NumberFormatException`). `isExactlyInstanceOf(...)` requires the **exact** runtime class. Pick based on whether subclasses should satisfy the test. ## Relationship to JUnit 5 JUnit 5 has `assertThrows(Type.class, executable)` returning the caught exception. AssertJ's variants are more **fluent** (chain message/cause checks inline) and consistent with the rest of your AssertJ assertions; the two interoperate freely — you can even feed `assertThrows`'s return value into `assertThat(...)`.
- What happens if you call the throwing method outside the lambda by mistake?It throws while the test is setting up the assertion, so the exception propagates out of the test as an error instead of being captured. The AssertJ assertion never runs, and the test fails with the raw exception rather than your intended check. Always put the risky call inside () -> ...
- How is assertThatThrownBy better than try/catch with fail()?It cannot silently pass: if nothing is thrown, assertThatThrownBy fails automatically. With try/catch you must remember to call fail() after the call, and forgetting it makes a non-throwing path pass incorrectly. AssertJ also chains type/message/cause checks fluently.
saying these in an interview costs you the question
- Calling the throwing method outside the lambda, causing a test error instead of an assertion
- Believing assertThatThrownBy passes when nothing is thrown (it fails)
- Using isInstanceOf when you meant the exact class (subclasses also pass)
- Confusing hasMessage (exact match) with hasMessageContaining (substring)