skip to content

assertThrows

assertThrows runs an executable, requires the expected exception, and hands it back so you can assert on message and cause. It replaced JUnit 4's expected attribute and ExpectedException rule precisely because it scopes the assertion to a single call.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

What is JUnit 5's assertThrows and how do you use it to verify that code throws an expected exception?

level: juniorimportance: must knowfreq 78%

answer

  1. Assertions.assertThrows(Type.class, executable)
  2. Executable = lambda/method ref, deferred run
  3. Passes on type OR subtype
  4. Returns the caught exception
  5. Lambda defers so JUnit can intercept the throw

basics

~20 s

assertThrows runs a piece of code and checks it throws the exception type you expect. You pass the expected exception class and a lambda holding the code. The test passes only if that code throws that type (or a subtype).

solid answer

~40 s

assertThrows is a static assertion in org.junit.jupiter.api.Assertions. You call assertThrows(ExpectedException.class, executable), where the executable is a lambda or method reference wrapping the code under test. JUnit invokes it; if it throws an instance of the expected type (or a subtype), the assertion passes and the caught exception is returned so you can inspect it. If nothing is thrown, or a different (non-assignable) type is thrown, the test fails. It replaced JUnit 4's clumsier mechanisms by scoping the expectation to exactly the lines you wrap, and by handing back the exception object for further assertions on its message, cause, or fields. A typical use captures the return value: var ex = assertThrows(IllegalArgumentException.class, () -> service.parse("")); then asserts on ex.getMessage().

code

java · 14 lines
java
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

class ParserTest {
    @Test
    void rejectsEmptyInput() {
        IllegalArgumentException ex = assertThrows(
            IllegalArgumentException.class,
            () -> Parser.parse("")          // executable: deferred until JUnit runs it
        );
        assertEquals("empty input", ex.getMessage());
    }
}

go deeper

for a junior

Knows assertThrows(Type.class, lambda) is how you test that code throws; can write a basic case and explain the lambda holds the code under test.

for a middle

Captures the returned exception and asserts on its message/cause; knows it matches subtypes and uses the message overload for clarity.

for a senior

Articulates the subtype-vs-exact-type semantics, contrasts with assertThrowsExactly, and explains why it superseded JUnit 4's @Test(expected) and ExpectedException rule.

for a principal

Sets team conventions: narrow exception assertions, asserting messages/causes without brittleness, and guidance on when exact-type matching matters for API contracts.

## What is an exception assertion? A **unit test** checks that a small piece of code behaves correctly. Sometimes the *correct* behavior is to **throw an exception** — for example, calling `Integer.parseInt("abc")` should throw `NumberFormatException`. To test that, you need a way to say: "running this code MUST throw THIS kind of exception, and if it does not, fail the test." **JUnit 5** (the testing framework, also called *JUnit Jupiter*) provides this via the static method **`assertThrows`**, found in the class `org.junit.jupiter.api.Assertions`. ## The signature ``` static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable) ``` Two arguments: - **`expectedType`** — the `Class` object of the exception you expect, written as `SomeException.class`. - **`executable`** — a functional interface `org.junit.jupiter.api.function.Executable` with one method `void execute() throws Throwable`. Because it is a *functional interface*, you supply it as a **lambda** `() -> ...` or a **method reference**. This is the actual code under test. *(A **functional interface** is an interface with exactly one abstract method, so a lambda can implement it. A **lambda** `() -> expr` is a short inline function.)* ## What it does, step by step 1. JUnit calls `executable.execute()` — i.e. it runs your wrapped code. 2. If that code throws a `Throwable` that **is an instance of** `expectedType` (the exact type *or any subclass* of it), the assertion **succeeds** and `assertThrows` **returns** that caught exception object (typed as `T`). 3. If the code throws a different type that is *not* assignable to `expectedType`, the assertion **fails** (and JUnit reports the unexpected exception). 4. If the code throws **nothing at all**, the assertion **fails** with a message like "Expected X to be thrown, but nothing was thrown." ## Why wrap code in a lambda? The lambda **defers execution**. The exception must be thrown *inside* `assertThrows`'s control so it can catch it. If you wrote the failing call directly, it would throw before `assertThrows` ever ran. The lambda packages the code so JUnit decides *when* to run it and can intercept the throw. ## Returning the exception A key feature: the method **returns the caught exception**, so you can keep asserting on it: ``` IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> parse("")); assertEquals("empty input", ex.getMessage()); ``` This lets you verify not just *that* it threw, but the **message**, **cause**, or any custom field. ## Optional message overload There is an overload with a third argument — a `String` or `Supplier<String>` failure message — shown when the assertion fails, e.g. `assertThrows(X.class, exec, "parse should reject empty input")`. ## Summary `assertThrows(Type.class, () -> codeThatShouldThrow())` is the standard JUnit 5 way to assert an exception. It passes on the type **or a subtype**, returns the exception for deeper checks, and scopes the expectation to exactly the wrapped lines.

  • What happens if the wrapped code throws no exception at all?
    The assertion fails with a message like 'Expected IllegalArgumentException to be thrown, but nothing was thrown.' Throwing nothing is a failure, not a pass.
  • Why must the code under test go inside a lambda?
    The lambda defers execution so assertThrows controls when the code runs and can catch the throw. A direct call would throw before assertThrows could intercept it.

It is like a bomb-disposal box: you put the suspicious code inside, JUnit sets it off in a contained space, and hands you back exactly which exception 'exploded' so you can examine it.

saying these in an interview costs you the question

  • Thinking the code can be called directly instead of inside the executable lambda — it would throw before assertThrows runs.
  • Believing assertThrows only passes on the exact type — it also passes on subtypes.
  • Forgetting that you can (and often should) capture and assert on the returned exception.
  • Confusing JUnit 5's Assertions.assertThrows with JUnit 4's @Test(expected=...).

context

open as a page

How do you assert on the message and cause of an exception captured by assertThrows, and what makes such assertions robust rather than brittle?

level: middleimportance: should knowfreq 42%

basics

~20 s

assertThrows returns the exception, so you store it in a variable and call getMessage() or getCause() on it, then assert with normal assertEquals/assertTrue. To stay robust, check a stable substring or the cause's type rather than the entire exact message text.

open as a page

What is the difference between assertThrows and assertThrowsExactly in JUnit 5?

level: middleimportance: should knowfreq 55%

basics

~20 s

assertThrows passes if the code throws the expected type or any subclass of it. assertThrowsExactly passes only if the thrown exception is exactly that class — a subclass fails. Use exactly when the precise type matters.

open as a page

Why did JUnit 5's assertThrows replace JUnit 4's @Test(expected=...) and the ExpectedException rule?

level: seniorimportance: should knowfreq 48%

basics

~20 s

@Test(expected=...) only checked the type and let any line in the test throw it, so it was imprecise and couldn't check the message easily. ExpectedException needed setup before the call. assertThrows scopes the check to exact lines and returns the exception to inspect.

open as a page

What are the common pitfalls when using assertThrows, including swallowed exceptions, multiple statements in the executable, and using it where exception testing isn't the goal?

level: seniorimportance: nice to knowfreq 33%

basics

~20 s

Don't put many lines in the lambda — an earlier line could throw the expected type and make the test pass for the wrong reason. Wrap only the call you expect to throw. And don't use assertThrows just to silence a checked exception in a test.

open as a page