Question
How can I write a JUnit test in an idiomatic way to verify that a specific exception is thrown?
For example, I could write a test like this:
@Test
public void testFooThrowsIndexOutOfBoundsException() {
boolean thrown = false;
try {
foo.doStuff();
} catch (IndexOutOfBoundsException e) {
thrown = true;
}
assertTrue(thrown);
}
However, this feels clumsy. I remember that JUnit provides a cleaner, more standard way to assert that code throws an exception, possibly through an annotation or an assertion method. What is the proper JUnit approach for this?
Short Answer
By the end of this page, you will understand how to test exceptions properly in JUnit, especially using assertThrows in modern JUnit and older approaches such as the expected attribute in JUnit 4. You will also learn why exception assertions matter, how to verify the exception message, and how to avoid common testing mistakes.
Concept
Testing exceptions is an important part of unit testing because many methods are expected to reject invalid input or fail in controlled ways.
In Java, exceptions are part of the method's behavior. If a method should throw an exception under certain conditions, that behavior should be tested just like a return value.
In JUnit, the goal is to make exception tests:
- clear
- concise
- precise about which exception is expected
- easy to maintain
The older manual try/catch approach works, but it is verbose and can hide mistakes. JUnit provides built-in ways to express this intent more clearly.
The most common modern approach is:
assertThrows(ExceptionType.class, () -> {
// code that should throw
});
This is better because:
- it clearly states the expected exception type
- it focuses only on the code that should throw
- it can return the exception object so you can inspect the message
- it avoids extra boolean flags and manual failure handling
This matters in real programming because many APIs rely on exceptions for validation and error reporting. Good tests confirm that invalid states are rejected in predictable ways.
Mental Model
Think of an exception test like checking that a security alarm goes off when someone opens the wrong door.
- The code under test is the door.
- The exception is the alarm.
- The JUnit assertion is your test that confirms the alarm really went off.
You do not just want to know that something happened. You want to know that:
- the alarm went off
- it was the correct alarm
- it happened at the correct moment
assertThrows is like saying: When I open this specific door, I expect this exact alarm to trigger.
Syntax and Examples
Modern JUnit: assertThrows
In JUnit 5, the standard way is assertThrows.
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
class FooTest {
@Test
void testFooThrowsIndexOutOfBoundsException() {
assertThrows(IndexOutOfBoundsException.class, () -> {
foo.doStuff();
});
}
}
You can also capture the exception and inspect it:
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
class CalculatorTest {
@Test
void divideByZeroThrowsException() {
ArithmeticException exception = assertThrows(ArithmeticException.class, () -> {
int result = 10 / 0;
});
assertEquals(, exception.getMessage());
}
}
Step by Step Execution
Consider this example:
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
class StringUtilsTest {
@Test
void substringWithInvalidIndexThrowsException() {
assertThrows(StringIndexOutOfBoundsException.class, () -> {
"hello".substring(10);
});
}
}
Step by step:
-
JUnit starts the test method
substringWithInvalidIndexThrowsException(). -
It evaluates
assertThrows(...). -
The expected exception type is
StringIndexOutOfBoundsException.class. -
JUnit runs the lambda body:
"hello".substring(10); -
Java tries to get a substring starting at index
10. -
The string only has length
5, so Java throwsStringIndexOutOfBoundsException.
Real World Use Cases
Exception assertions are common in real applications whenever code must reject invalid input or unsafe operations.
Input validation
assertThrows(IllegalArgumentException.class, () -> {
userService.createUser("");
});
Used when methods should reject blank names, invalid IDs, or negative values.
API request validation
A controller or service may throw an exception if required request data is missing.
assertThrows(IllegalStateException.class, () -> {
orderService.submitOrder(null);
});
Collection bounds checks
assertThrows(IndexOutOfBoundsException.class, () -> {
list.get(99);
});
Useful for verifying defensive code around lists, arrays, and indexing.
Parsing and conversion
assertThrows(NumberFormatException.class, () -> {
Integer.parseInt("abc");
});
Used when parsing user input, CSV files, JSON values, or configuration.
Domain rule enforcement
A business rule might forbid invalid actions.
assertThrows(IllegalArgumentException.class, () -> {
bankAccount.withdraw(-50);
});
These tests document the expected failure behavior of your system.
Real Codebase Usage
In real projects, developers often use exception assertions as part of validation and defensive programming.
Common patterns
Guard clauses
Methods often fail early when arguments are invalid:
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
Test:
assertThrows(IllegalArgumentException.class, () -> person.setAge(-1));
Verifying exception messages
When the message helps users or developers diagnose a problem, teams often test it too:
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> {
validator.requirePositive(-5);
});
assertEquals("Value must be positive", ex.getMessage());
Narrow test scope
Developers usually keep only the throwing statement inside assertThrows:
Common Mistakes
1. Catching the exception manually without failing when nothing is thrown
Broken example:
@Test
void testSomething() {
try {
foo.doStuff();
} catch (IndexOutOfBoundsException e) {
// test passes silently
}
}
Problem:
- if no exception is thrown, the test still passes
Better:
assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff());
2. Wrapping too much code inside the assertion
Broken example:
assertThrows(IllegalArgumentException.class, () -> {
setupDatabase();
loadConfig();
service.process(input);
});
Problem:
- the exception might come from setup code instead of
service.process(input)
Better:
setupDatabase();
loadConfig();
assertThrows(IllegalArgumentException.class, () -> service.process(input));
3. Asserting the wrong exception type
Broken example:
assertThrows(Exception.class, () -> foo.doStuff());
Comparisons
Exception assertion approaches in JUnit
| Approach | JUnit Version | Good For | Limitations |
|---|---|---|---|
Manual try/catch | Any | Understanding the raw mechanics | Verbose, easier to get wrong |
@Test(expected = ...) | JUnit 4 | Simple exception type checks | Cannot easily inspect message or exact throw location |
ExpectedException rule | JUnit 4 | Type and message checks | More boilerplate, older style |
assertThrows(...) | JUnit 5 and later style | Clear, flexible, modern testing | Requires lambda syntax |
assertThrows vs manual
Cheat Sheet
Quick reference
JUnit 5
assertThrows(MyException.class, () -> {
methodCall();
});
Capture the exception:
MyException ex = assertThrows(MyException.class, () -> methodCall());
assertEquals("message", ex.getMessage());
JUnit 4
@Test(expected = MyException.class)
public void testSomething() {
methodCall();
}
Best practices
- Prefer
assertThrowsin modern code. - Assert the most specific exception type possible.
- Put only the statement that should throw inside the lambda.
- Check the message if it matters.
- Avoid boolean flags like
thrown = true. - Avoid broad exception types like
Exception.class.
Common failure cases
- No exception thrown -> test fails
- Wrong exception type thrown -> test fails
- Exception thrown before the intended line -> test may be misleading if scope is too large
FAQ
How do I check that an exception is thrown in JUnit 5?
Use assertThrows:
assertThrows(IllegalArgumentException.class, () -> service.run());
How do I assert the exception message in JUnit?
Capture the returned exception:
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> service.run());
assertEquals("Bad input", ex.getMessage());
What is the JUnit 4 way to expect an exception?
A common JUnit 4 approach is:
@Test(expected = IllegalArgumentException.class)
public void testRun() {
service.run();
}
Why is assertThrows better than @Test(expected = ...)?
Because it is more precise and lets you inspect the exception object, including its message.
Should I catch the exception manually in a test?
Usually no. Manual try/catch is more verbose and easier to write incorrectly.
Can I assert a parent exception type?
Yes, but prefer the most specific type possible so your test is stricter and more meaningful.
Mini Project
Description
Build a small validation utility and write JUnit tests that prove invalid input throws the correct exceptions. This project demonstrates how exception assertions are used in everyday code when validating user input or enforcing business rules.
Goal
Create a Java validator class and test that it throws the correct exceptions for invalid values using idiomatic JUnit assertions.
Requirements
- Create a method that rejects blank usernames.
- Create a method that rejects negative ages.
- Write tests that assert the correct exception type is thrown.
- Verify at least one exception message.
- Use
assertThrowsinstead of manualtry/catch.
Keep learning
Related questions
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Choosing a @NotNull Annotation in Java: Validation vs Static Analysis
Learn how Java @NotNull annotations differ, when to use each one, and how to choose between validation, IDE hints, and static analysis tools.
Convert a Java Stack Trace to a String
Learn how to convert a Java exception stack trace to a string using StringWriter and PrintWriter, with examples and common mistakes.