AI InterviewSearch…Search problems, topics, or pages…Ctrl KGet started
Skip to content

12 Exceptions / Lesson 2 of 6

Checked versus unchecked — the rules and the judgement

13 min · +10 XP · 2 exercises · 5 questions

Read the text13 min
Exercises0/2
Quiz5 q

Quick answer

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.

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 Exception but not RuntimeException; the compiler requires catch-or-declare.
  • "Why does Files.readString need a try but Integer.parseInt does not?" IOException is checked; NumberFormatException is 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 IOException if the parent declares none?" No.

Key takeaways

  • Checked = Exception minus RuntimeException; callers must catch or declare. Unchecked propagates silently.
  • throws propagates 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 Exception in 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.

Mark as readSign in to keep your place.

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 Exception but not RuntimeException; the compiler requires catch-or-declare.
  • "Why does Files.readString need a try but Integer.parseInt does not?" IOException is checked; NumberFormatException is 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 IOException if the parent declares none?" No.

Key takeaways

  • Checked = Exception minus RuntimeException; callers must catch or declare. Unchecked propagates silently.
  • throws propagates 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 Exception in 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 10

Wrap 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-db

In this module: Exceptions

← The exception hierarchy and how to read a stack trace · try, catch and finally — the exact semantics →