Checked vs unchecked in Java
Checked must be handled or declared; unchecked need not be.
Thread.sleep(10) in a plain method and your code refuses to compile. Divide by zero and it compiles happily. What's the difference?The compiler's checklist
Checked exceptions are Exception subclasses outside RuntimeException — IOException, InterruptedException. For these the compiler enforces a rule: handle or declare. Either catch it, or add throws to the method signature.
void load() throws IOException { // declare
Files.readString(Path.of("a.txt"));
}A short nap
Thread.sleep can throw InterruptedException. What happens?
void nap() {
Thread.sleep(10);
}
void main() {
nap();
System.out.println("rested");
}restedCompile errorThrows InterruptedException
Show the answer
InterruptedException is checked. nap() neither catches it nor declares throws InterruptedException, so the compiler rejects it.
Unchecked: no paperwork
Unchecked exceptions — RuntimeException, Error and their subclasses — need neither catch nor throws. A method that may throw NullPointerException doesn't have to declare it. The compiler stays quiet, and the problem shows up at runtime.
Compiles fine, then…
No throws clause anywhere. What happens?
void parse() {
throw new IllegalStateException("bad");
}
void main() {
parse();
System.out.println("done");
}Compile error: parse() must declare throwsThrows IllegalStateExceptiondone
Show the answer
IllegalStateException is unchecked, so it compiles. At runtime it propagates out of main before done is printed.
Which kind should you throw?
Your library reads an optional config file. It may legitimately be missing, and callers can recover with defaults. Checked or unchecked?
Think about it, then reveal the answer
Checked, like IOException. Java's philosophy: checked for recoverable problems outside your control (missing files, network), so callers must plan for them; unchecked for bugs (an unexpected null, a bad index).
Lambdas and checked exceptions
Function, Predicate and friends declare no checked exceptions, so a lambda calling Files.readString (which throws IOException) won't compile inside map. Catch inside the lambda, or rethrow as UncheckedIOException.
paths.stream()
.map(p -> Files.readString(p)) // error
.toList();A long-running debate
Many frameworks convert checked exceptions into unchecked ones: Spring turns SQLException into its DataAccessException family, so business code isn't buried in throws clauses. Knowing both kinds lets you read such code — and choose deliberately in your own APIs.
Key takeaways
- Checked: handle or declare (IOException, InterruptedException)
- Unchecked: RuntimeException and Error subclasses
- The compiler only enforces checked exceptions
- Standard functional interfaces (Function, Predicate) can't throw checked exceptions
💡 A checked exception is a road sign the map makes you acknowledge; an unchecked one is a pothole nobody warned you about.
C# and Kotlin deliberately left checked exceptions out — Java is one of the very few mainstream languages where the compiler enforces them.
Practice questions
What does this print?
void load() {
throw new IOException("missing");
}
void main() {
load();
}- Compile error
- Throws IOException
- Prints missing
Check your answer
Compile error. IOException is checked. load() neither catches it nor declares throws IOException, so the compiler rejects it.
What does this print?
void parse() {
throw new IllegalStateException("bad");
}
void main() {
parse();
System.out.println("done");
}- Compile error: parse() must declare throws
- Throws IllegalStateException
- done
- bad
Check your answer
Throws IllegalStateException. IllegalStateException is unchecked, so it compiles without throws. At runtime it propagates out of main before "done".