Checked vs Unchecked Exceptions in Java: When to Use Each
Checked exceptions must be caught or declared with throws; unchecked ones, RuntimeException and its subclasses, need neither. The rules and which to choose.
- Course: Java study plan
- Module: Exceptions
- Kind: Lesson
- Reading time: 13 min
- Runtime: OpenJDK 18
What is the difference between checked and unchecked exceptions in Java?
A checked exception in Java is a subclass of Exception that is not a RuntimeException, such as IOException; the compiler requires every method that can throw it to catch it or declare it with throws. An unchecked exception — RuntimeException, its subclasses, and Error — needs neither. Checked exceptions suit expected, recoverable conditions; unchecked ones suit programming errors.
Lesson
Java is almost alone among mainstream languages in having checked exceptions: failures the compiler forces you to acknowledge at every call site. Whether that was a good idea is a twenty-year argument; that you must understand the rules is not. This lesson covers what the compiler demands, how throws propagates, how to convert between the two kinds, and the guidelines the JDK and Effective Java use to decide which kind a new exception should be.
The rule
If a method can throw a checked exception — one that is an Exception but not a RuntimeException — it must either catch it or declare it with throws:
static String read(Path p) throws IOException { // declared: the caller must deal with it
return Files.readString(p);
}
static String readOrEmpty(Path p) { // caught: the caller is not bothered
try {
return Files.readString(p);
} catch (IOException e) {
return "";
}
}
static String broken(Path p) {
return Files.readString(p); // compile error: unreported exception IOException; must be caught or declared to be thrown
}
throws lists checked exceptions a method may let escape; unchecked ones may be listed for documentation but need not be. A method that declares throws Exception forces every caller to handle Exception — which is why broad declarations are a smell outside main and test code.
Propagation
An uncaught exception unwinds the stack: the current method ends, its caller's catch blocks are searched, then the caller's caller, until a handler is found or the thread dies (printing the trace). Each method on that path must have declared the checked exception. The chain of throws is the compile-time shadow of that run-time unwinding.
Unchecked exceptions unwind the same way — the difference is purely that the compiler does not track them.
Overriding and throws
An override may throw the same, fewer or narrower checked exceptions than the overridden method — never broader or new ones (Module 8). Consequence: an interface method that declares nothing (Runnable.run, Function.apply) can never be implemented by code that lets a checked exception escape. This is the source of most friction between checked exceptions and lambdas.
Converting checked to unchecked
When a checked exception cannot happen (a hard-coded URI), should not be recoverable (a config file that must exist), or must cross a boundary that does not allow it (a lambda), wrap it:
try {
return Files.readString(path);
} catch (IOException e) {
throw new UncheckedIOException("cannot read " + path, e); // keep the cause!
}
catch (InterruptedException e) {
Thread.currentThread().interrupt(); // restore the flag — special case
throw new IllegalStateException("interrupted", e);
}
UncheckedIOException exists for exactly this; for other checked exceptions, IllegalStateException(msg, cause) or a custom unchecked exception. Always pass the original as the cause — losing it loses the real stack trace.
Converting the other way (throwing a checked exception from a place that caught an unchecked one) is rare and usually wrong.
The throws clause on main
public static void main(String[] args) throws Exception is the accepted way to let everything propagate in small programs and exercises: the JVM prints the trace and exits non-zero. In real applications main catches, logs and exits deliberately.
The design guideline: which kind?
The JDK's rule of thumb, refined by Effective Java items 70–71:
Checked when the condition is expected in a correct program and the caller can plausibly recover: a file that may not exist, a network that may fail, a user-entered date that may be malformed (DateTimeParseException is unchecked, though — the JDK is inconsistent). The compiler-enforced acknowledgement is the feature.
Unchecked when the condition is a programming error the caller cannot recover from at that point: null arguments, bad indices, illegal state, violated preconditions. Forcing every caller to catch (NullPointerException) would be noise; the fix is in the code, not the handler.
Modern practice leans unchecked for almost everything an application defines: frameworks (Spring, Jakarta) wrap data-access exceptions unchecked; Kotlin and Scala dropped checked exceptions entirely. The argument against checked: they leak implementation details up the stack (throws SQLException on a business method), they do not compose with lambdas and streams, and in practice most catch blocks just wrap and rethrow. The argument for: they document failure modes in the type system, and IOException genuinely should not be forgotten. A reasonable position: use checked exceptions for recoverable conditions at API boundaries you control, unchecked for everything else, and never throw Exception or Throwable.
Declaring precisely
void load() throws FileNotFoundException, ParseException // specific: callers can handle each
void load() throws Exception // lazy: callers learn nothing and must catch everything
Declare the most specific types. If a method throws three unrelated checked exceptions, consider whether it is doing three things, or whether one domain exception should wrap them.
Checked exceptions and lambdas
paths.stream().map(p -> Files.readString(p)) // compile error: IOException inside Function.apply
Options: catch and wrap inside the lambda (UncheckedIOException); a helper static String readUnchecked(Path p); or a custom ThrowingFunction interface with an adapter. The first two are the everyday answers. Streams and checked exceptions do not mix; design library methods called from lambdas to throw unchecked.
Interview angle
- "What is a checked exception?" A subclass of
Exceptionbut notRuntimeException; the compiler requires catch-or-declare. - "Why does
Files.readStringneed atrybutInteger.parseIntdoes not?"IOExceptionis checked;NumberFormatExceptionis unchecked. - "How do you throw a checked exception from a lambda?" You cannot let it escape; wrap it in an unchecked exception with the cause.
- "When would you create a checked exception?" For a recoverable, expected condition at an API boundary — rarely, in modern code.
- "Can an override add
throws IOExceptionif the parent declares none?" No.
Key takeaways
- Checked =
ExceptionminusRuntimeException; callers must catch or declare. Unchecked propagates silently. throwspropagates the obligation up the call chain; overrides may narrow, never broaden.- Wrap checked in unchecked (
UncheckedIOException,IllegalStateException) at boundaries and in lambdas — always with the cause. - Checked for recoverable, expected conditions; unchecked for programming errors — and modern code leans unchecked.
- Declare specific types; never
throws Exceptionin an API.
Common questions
What does the throws keyword do in Java?
throws in a method signature lists the checked exceptions the method may let escape, passing the obligation to catch or declare them on to its callers. Unchecked exceptions may be listed for documentation but need not be.
How do you fix "unreported exception IOException; must be caught or declared to be thrown"?
Either catch the IOException in a try/catch around the call, or add throws IOException to the enclosing method so its callers deal with it. Where neither fits — inside a lambda, for example — wrap it in an UncheckedIOException with the original as the cause.
How do you throw a checked exception from a lambda in Java?
You cannot let it escape, because Function.apply, Runnable.run and the other standard functional methods declare no checked exceptions. Catch it inside the lambda and rethrow it wrapped in an unchecked exception, such as UncheckedIOException, passing the original as the cause.
Can an overriding method throw a broader checked exception?
No. An override may throw the same, fewer or narrower checked exceptions than the method it overrides, never broader or new ones. So an override cannot add throws IOException when the parent method declares none.
Should I use checked or unchecked exceptions?
Use checked exceptions for expected conditions a caller can plausibly recover from at an API boundary, such as a file that may not exist; use unchecked ones for programming errors like null arguments or bad indices. Modern Java leans unchecked, and an API should never declare throws Exception.
Exercises
Declare, catch or wrap
Write a checked exception class QuotaExceededException extends Exception with a message. Write static void consume(int used, int limit) throws QuotaExceededException that throws when used > limit. Then write static void consumeUnchecked(int used, int limit) that calls consume and wraps the checked exception in an IllegalStateException with the cause.
Read an integer limit, an integer n, and n usage values. For each value call consumeUnchecked; catch IllegalStateException and print wrapped: <cause class simple name>: <cause message>, otherwise print ok <used>.
Example: 10, 2, 5 12 →
ok 5
wrapped: QuotaExceededException: used 12 of 10Wrap the checked one
Write static String readConfig(String name) throws java.io.IOException that throws new IOException("missing: " + name) when the name starts with missing- and otherwise returns name + "=ok". Then write static String load(String name) with no throws clause that calls readConfig and wraps any IOException in an UncheckedIOException. Read n names; for each call load and print the value, or catch the UncheckedIOException and print unchecked: <cause message>.
Example: 2 then app missing-db →
app=ok
unchecked: missing: missing-dbIn this module: Exceptions
- The exception hierarchy and how to read a stack trace
- Checked versus unchecked — the rules and the judgement (this lesson)
- try, catch and finally — the exact semantics
- try-with-resources and AutoCloseable
- Custom exceptions and exception chaining
- Exception handling that survives production
- Checkpoint — Exceptions
← The exception hierarchy and how to read a stack trace · try, catch and finally — the exact semantics →