Checked vs unchecked exceptions

javac happily compiles 10 / 0, yet refuses a harmless-looking Thread.sleep(500). The reason is the split between checked and unchecked exceptions. You'll learn which is which, what the compiler checks and what it doesn't, when each kind is the right choice, how to wrap one in the other without losing the original cause, and then practise with 11 problems.

Module 9 · Exception Handling⏱ 21 minIntermediate11 practice problems
1 The story

The compiler that wouldn't let go

Riya's fee-reminder program reads due dates like “15-07-2025” with SimpleDateFormat. It won't compile: unreported exception ParseException; must be caught or declared to be thrown. Yet last week her Integer.parseInt code compiled without any complaint, even though it could also fail on bad text.

Integer.parseInt(text) dateFormat.parse(text) javac checks the throws clauses compiles NumberFormatException: unchecked stopped ParseException is checked: catch it or declare it myphatshala.in
Riya

Sir, both methods can fail on bad text. Why does the compiler force me to handle only one of them?

Arjun Sir

Because the people who designed those methods made different choices. parse declares a checked exception: “this can fail for reasons you must plan for.” parseInt throws an unchecked one: “if you pass bad input, that's a bug or bad data; handle it if you want.”

Riya

So checked ones happen at compile time?

Arjun Sir

No! That's the classic mistake. All exceptions happen at run time. “Checked” only means the compiler checks that you've planned for it, by catching it or declaring it with throws.

2 Definition

What are checked and unchecked exceptions?

Formal definition (write this in exams)

Checked exceptions are Throwable classes that are not subclasses of RuntimeException or Error (for example IOException, ParseException, ClassNotFoundException). For these, the compiler enforces the catch-or-declare rule: any code that can throw one must either catch it or declare it in the method's throws clause, otherwise compilation fails. Unchecked exceptions are RuntimeException, Error and their subclasses; the compiler does not require them to be caught or declared.

In simple words

Checked = “this might go wrong even if your code is perfect (missing file, network down), so show me your plan.” Unchecked = “this usually means a bug or bad input; I won't force you, but fix the cause.”

Words you'll meet in this lesson

Checked exceptionMust be caught or declared, or the code won't compile.
Unchecked exceptionRuntimeException, Error and their subclasses. No rule.
Catch-or-declareThe compiler's rule for checked exceptions. Also called “handle or specify”.
throws clausevoid read() throws IOException: “callers, this may reach you.”
WrappingThrowing a new exception that carries the original one inside it.
CauseThe original exception stored in a wrapper; read with getCause().

Checked vs unchecked at a glance

CheckedUnchecked
Class hierarchyException, but not RuntimeExceptionRuntimeException or Error (and subclasses)
Compiler forces catch or throws?Yes: unreported exception …No
When does it happen?At run timeAt run time
Typical meaningOutside problem you should plan forBug, bad argument or bad data
ExamplesIOException, FileNotFoundException, ParseException, SQLException, ClassNotFoundException, InterruptedExceptionNullPointerException, ArithmeticException, IllegalArgumentException, NumberFormatException, IndexOutOfBoundsException
Usual handlingCatch where you can recover, or declare and let a caller decidePrevent with checks; catch at a boundary (request, record, job)

Exam tip: “Checked exceptions are checked at compile time” means the handling is checked at compile time. The exception itself is still thrown at run time. Also: Error is unchecked even though it is not a RuntimeException.

3 Types and ways to use it

Same job, two different designs

Checked: outside world

Files, networks, databases, dates typed by users with the old API. The failure isn't your bug, so you must have a plan.

IOException, SQLException

Unchecked: bugs and bad arguments

Null references, wrong indexes, invalid values passed to a method. Prevent them with checks rather than catch blocks everywhere.

RuntimeException

Wrapped: checked inside unchecked

Catch a checked exception and rethrow it as an unchecked one with the original as its cause. Common in modern frameworks and lambdas.

new X(msg, cause)

Parsing a date: old API (checked) vs new API (unchecked)

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;

public class Main {
    public static void main(String[] args) {
        // Old API: parse() declares the checked ParseException
        try {
            new SimpleDateFormat("yyyy-MM-dd").parse("2025/01/26");
        } catch (ParseException e) {
            System.out.println("Checked:   " + e);
        }
        // New API: LocalDate.parse() throws the unchecked DateTimeParseException
        try {
            LocalDate.parse("2025/01/26");
        } catch (DateTimeParseException e) {
            System.out.println("Unchecked: " + e);
        }
    }
}
Checked: java.text.ParseException: Unparseable date: "2025/01/26" Unchecked: java.time.format.DateTimeParseException: Text '2025/01/26' could not be parsed at index 4

Delete the first try/catch and the program no longer compiles. Delete the second and it compiles fine, but would crash at run time on bad input. java.time arrived in Java 8 and, like most modern APIs, chose unchecked exceptions.

When should you use which?

SituationChooseExample
Caller passed an invalid valueUncheckedIllegalArgumentException("Invalid GST slab: 15%")
Object is in the wrong state for this callUncheckedIllegalStateException("Order already shipped")
A failure the caller can reasonably recover from and must think aboutCheckedInsufficient funds in a banking API (Lesson 9.5)
You can't do anything useful with a library's checked exception hereWrap in unchecked, keep the causenew UncheckedIOException("Could not load fees", e)

Lambdas and checked exceptions don't mix: names.forEach(n -> Thread.sleep(10)); fails with unreported exception InterruptedException, because the functional interfaces used by forEach and streams don't declare any checked exceptions. Catch inside the lambda or wrap the exception.

4 Step by step

How javac applies the catch-or-declare rule

  1. javac reads the called method's signature

    public static void sleep(long millis) throws InterruptedException
    public static int parseInt(String s) throws NumberFormatException
    Both methods list an exception after throws. javac looks at each one's class: InterruptedException extends Exception directly, so it is checked. NumberFormatException is a RuntimeException, so javac ignores it for the rule.
  2. No catch and no throws? Compile error

    Main.java:4: error: unreported exception InterruptedException; must be caught or declared to be thrown
            Thread.sleep(500);
                        ^
    This check happens only in the compiler. No class file is produced, so the program never runs.
  3. Option A: catch it where you can deal with it

    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        System.out.println("Reminder delay interrupted");
    }
    The call is inside a try with a catch for that type (or a parent such as Exception), so the rule is satisfied.
  4. Option B: declare it and let the caller decide

    static void sendReminder(String student) throws InterruptedException {
        Thread.sleep(500);
    }
    Now every caller of sendReminder faces the same rule. The obligation moves up one level, until some method catches it. (More on throws in Lesson 9.4.)
  5. Option C: wrap it in an unchecked exception with its cause

    catch (ParseException e) {
        throw new IllegalArgumentException("Bad due date " + text, e);
    }
    The new object stores the original in its cause field. A stack trace then shows both parts:
    java.lang.IllegalArgumentException: Bad due date 31-06-2025
    	at Main.saveDueDate(Main.java:11)
    	at Main.main(Main.java:17)
    Caused by: java.text.ParseException: Unparseable date: "31-06-2025"
    	at java.base/java.text.DateFormat.parse(DateFormat.java:399)
    	at Main.saveDueDate(Main.java:9)
    	... 1 more
5 See it happen

Compile time vs run time: both views

Left: the line and what javac or the JVM says. Right: what javac checks before the program exists, and what the JVM does once it runs.

The compiler's check, then the run 6 steps
What you see Thread.sleep(500); try {…} catch (InterruptedException e) int share = fee / count; // run it with count = 0 throw new IllegalArgument…(msg, e); e.getCause() error: unreported exception InterruptedException; must be caught or declared to be thrown javac: compiled OK javac: compiled OK (no checked exception possible) Bad due date 31-06-2025 cause: ParseException Exception in thread "main" java.lang.ArithmeticException: / by zero Checked exception, no plan: no program at all. Caught: the compiler is satisfied. Division may fail, but javac doesn't insist. Unchecked problems show up only at run time. Wrapped: an unchecked exception is thrown. The original is still inside, as the cause. myphatshala.in
Behind the screen COMPILE TIME: javac Thread.sleep(long) throws InterruptedException idiv may throw ArithmeticException Is it a RuntimeException or an Error? No → CHECKED → catch or declare? Yes → UNCHECKED → no rule No catch, no throws → compile error Inside try with matching catch → OK Nothing to check → OK RUN TIME: the JVM #1 ArithmeticException: / by zero thrown by idiv; no handler found The JVM treats every Throwable the same. “Checked” exists only in the compiler. #2 IllegalArgumentException "Bad due date 31-06-…" #1 ParseException "Unparseable date: …" cause No class file is written; nothing can run. The catch covers the call: rule satisfied. Unchecked: javac does not look further. Same throw and search as any other exception. The wrapper's cause field points to #1. getCause() follows that reference. myphatshala.in
6 Real-world use

Checked and unchecked in real systems

Big codebases mix both styles. Knowing which one an API uses tells you what you must handle.

Watch it with an example: an exception travelling through three layers

A web app's database layer gets a checked SQLException. Each layer decides what to do: wrap it, pass it on, or turn it into an HTTP response.

SQLException → wrapped → HTTP 500 6 steps
Animated example Controllerweb layer FeeServicebusiness rules FeeDaodatabase code #1 SQLException (checked) "connection refused" #2 FeeLoadException (unchecked) cause → #1 HTTP 500 "Could not load fees" log: FeeLoadException … Caused by: SQLException myphatshala.in

1. Reading files and configuration

File APIs in java.io and java.nio.file throw the checked IOException. Start-up code reading application.properties or a CSV upload must decide: retry, use defaults, or stop with a clear error.

Config file→IOException (checked)→Defaults or fail fast

2. Databases: SQLException and friends

Plain JDBC throws the checked SQLException from almost every call. Spring translates it into its unchecked DataAccessException family, so service code isn't full of try/catch blocks.

JDBC call→SQLException → DataAccessException→Handled at one boundary

3. Streams and lambdas

Lambdas can't throw checked exceptions through map or forEach. The JDK itself wraps: Files.lines(path) reports read failures during the stream as the unchecked UncheckedIOException.

IOException→UncheckedIOException(e)→Works inside streams
rate = 15%400 Bad Request

4. Validating API input

Service methods reject bad arguments with unchecked exceptions like IllegalArgumentException. A global handler in the web layer turns them into an HTTP 400 response with the message.

Bad input→IllegalArgumentException→400 response

Examples are simplified. Teams usually agree on a convention, often “unchecked for business and validation errors, wrap library checked exceptions at the boundary”, and apply it consistently.

7 Run the code

Riya's due-date converter

import java.text.ParseException;
import java.text.SimpleDateFormat;

public class Main {
    static String toIso(String text) throws ParseException {
        SimpleDateFormat in = new SimpleDateFormat("dd-MM-yyyy");
        in.setLenient(false);
        return new SimpleDateFormat("yyyy-MM-dd").format(in.parse(text));
    }

    public static void main(String[] args) {
        String[] dueDates = {"15-07-2025", "31-06-2025", "15/08/2025"};
        for (String d : dueDates) {
            try {
                System.out.println(d + " -> " + toIso(d));
            } catch (ParseException e) {
                System.out.println(d + " -> rejected: " + e.getMessage());
            }
        }
    }
}
15-07-2025 -> 2025-07-15 31-06-2025 -> rejected: Unparseable date: "31-06-2025" 15/08/2025 -> rejected: Unparseable date: "15/08/2025"
  • throws ParseExceptionOption B: toIso declares the checked exception instead of catching it.
  • in.setLenient(false)Without it, 31 June would quietly become 1 July.
  • catch (ParseException e)main is where we know what to do: reject that date and move on.
  • e.getMessage()Names the exact text that failed, useful for the user and for logs.
8 Line by line

Wrapping a checked exception on its way up

A checked ParseException is thrown two calls deep, caught one level up and wrapped in an unchecked exception that keeps it as its cause.

Worked examples: real problems, solved and traced in memory

Three situations where the checked or unchecked choice changes the code you must write. Run each one, then step through it and watch exception objects (and their causes) appear on the heap while frames are popped.

Example 1Travel · booking

A checked ParseException travels two frames

Problem statement

A booking form sends the journey date as text. parseJourney converts it with SimpleDateFormat, whose parse throws the checked ParseException. Neither helper can do anything useful about a bad date, so they declare it, and main handles it.

Input
journey dates: "14-11-2025", then "31-11-2025" (November has 30 days)
Expected output
Booked Kavya on 14-11-2025
Booking failed: Unparseable date: "31-11-2025"

Approach

  1. parseJourney declares throws ParseException instead of catching.
  2. book calls it and does not catch either, so the compiler makes it declare throws ParseException too.
  3. main catches it: the compile-time chain ends there. At run time, the second date fails and the exception skips both helper frames.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 2Tax · GST billing

An unchecked exception for a bad argument

Problem statement

A billing module computes GST. Only the slabs 0, 5, 12, 18 and 28 percent are valid. A wrong rate is a programming or data error, so gstAmount throws the unchecked IllegalArgumentException. Callers are not forced to catch it; here main catches it to print a clear message.

Input
item 1: Rs 1000 at 18%
item 2: Rs 500 at 15% (not a valid slab)
Expected output
Headphones: Rs 1000 + GST Rs 180
Billing error: Invalid GST slab: 15%

Approach

  1. gstAmount checks the rate and throws if it isn't a valid slab. No throws clause is needed for an unchecked exception.
  2. billLine calls it without any try or throws, and the compiler is happy.
  3. At run time the second line throws; the exception passes through billLine and is caught in main.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 3Platform · start-up

Wrap a checked exception, keep the cause

Problem statement

At start-up an app loads its payment plugin class by name. Class.forName throws the checked ClassNotFoundException. The start-up code wraps it in an unchecked IllegalStateException with a clear message, keeping the original as the cause.

Input
plugin class name = "in.myshop.pay.UpiPlugin" (not on the classpath)
Expected output
Start-up failed: Payment plugin missing
Root cause: java.lang.ClassNotFoundException: in.myshop.pay.UpiPlugin

Approach

  1. loadPlugin catches the checked exception right where it happens.
  2. It throws a new IllegalStateException(message, cause); the original object becomes its cause.
  3. main catches the wrapper and uses getCause() to reach the original problem.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

9 Quiz and revision

Check yourself

Quick revision

Lesson 9.3 in one screen

  • Checked = Exception but not RuntimeException. Unchecked = RuntimeException, Error and subclasses.
  • Checked exceptions must be caught or declared (throws), else: unreported exception … must be caught or declared to be thrown.
  • Every exception happens at run time; the check is a compile-time rule only.
  • Catching a checked exception the try can't throw is a compile error; catch (Exception e) is always allowed.
  • Use unchecked for bad arguments and bugs; checked for recoverable outside failures the caller must consider.
  • Wrap with new XException("message", e) to keep the cause; read it with getCause().
  • Lambdas in streams can't throw checked exceptions: catch or wrap inside.

Interview and viva questions

What is the difference between checked and unchecked exceptions?

Checked exceptions (subclasses of Exception that are not RuntimeException) must be caught or declared, and the compiler enforces this. Unchecked exceptions (RuntimeException, Error and their subclasses) have no such rule. Both are thrown at run time; the difference is only whether the compiler forces you to plan for them.

Why did Java's designers create checked exceptions?

To make failures that are part of a method's normal contract, such as a missing file or a dropped network connection, visible in its signature, so that callers cannot forget them. The trade-off is extra code, and the rule doesn't work well with lambdas, which is why many modern APIs prefer unchecked exceptions.

Is it good practice to convert checked exceptions to unchecked?

Often yes, at a boundary where the code cannot recover, for example inside a lambda or a layer that only passes the problem upwards. Always keep the original as the cause, and choose a meaningful wrapper (UncheckedIOException, IllegalStateException or your own type) with a clear message.

Can a method declare an unchecked exception in throws?

Yes. Integer.parseInt declares throws NumberFormatException. It documents the behaviour, but the compiler ignores unchecked types for the catch-or-declare rule, so callers aren't forced to catch it.

Does the JVM treat checked exceptions differently at run time?

No. The JVM throws and handles all Throwables the same way. Checked exceptions are a rule of the Java compiler only, which is why other JVM languages such as Kotlin have no checked exceptions at all.

Practice problems

Practise recognising checked and unchecked exceptions, satisfying the compiler, and wrapping one kind in the other without losing the cause. The last three are real business tasks.

P1Easy

Checked or unchecked?

Write isChecked(Throwable t) that returns true only for checked exceptions, and test it on five exception objects.

Input
IOException, NumberFormatException, ParseException, OutOfMemoryError, ClassNotFoundException
Expected output
IOException: checked
NumberFormatException: unchecked
ParseException: checked
OutOfMemoryError: unchecked
ClassNotFoundException: checked
Show solution and explanation
public class Main {
    static boolean isChecked(Throwable t) {
        return !(t instanceof RuntimeException) && !(t instanceof Error);
    }

    public static void main(String[] args) {
        Throwable[] list = {
            new java.io.IOException(),
            new NumberFormatException(),
            new java.text.ParseException("bad", 0),
            new OutOfMemoryError(),
            new ClassNotFoundException()
        };
        for (Throwable t : list) {
            System.out.println(t.getClass().getSimpleName() + ": " + (isChecked(t) ? "checked" : "unchecked"));
        }
    }
}
IOException: checked NumberFormatException: unchecked ParseException: checked OutOfMemoryError: unchecked ClassNotFoundException: checked

How it works: the rule is about the class hierarchy only. Anything under RuntimeException or Error is unchecked; every other Throwable is checked. ParseException and ClassNotFoundException extend Exception directly or through a non-runtime parent, so they are checked.

P2Easy

Handle a checked ParseException

Convert dates from dd-MM-yyyy to yyyy-MM-dd with SimpleDateFormat. Its parse method throws the checked ParseException, so the code must catch it.

Input
"26-01-2025", "26/01/2025", "30-02-2025"
Expected output
26-01-2025 -> 2025-01-26
26/01/2025 -> Unparseable date: "26/01/2025"
30-02-2025 -> Unparseable date: "30-02-2025"
Show solution and explanation
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class Main {
    public static void main(String[] args) {
        SimpleDateFormat in = new SimpleDateFormat("dd-MM-yyyy");
        in.setLenient(false);
        SimpleDateFormat out = new SimpleDateFormat("yyyy-MM-dd");
        String[] dates = {"26-01-2025", "26/01/2025", "30-02-2025"};
        for (String d : dates) {
            try {
                System.out.println(d + " -> " + out.format(in.parse(d)));
            } catch (ParseException e) {
                System.out.println(d + " -> " + e.getMessage());
            }
        }
    }
}
26-01-2025 -> 2025-01-26 26/01/2025 -> Unparseable date: "26/01/2025" 30-02-2025 -> Unparseable date: "30-02-2025"

How it works: remove the try/catch and javac stops with unreported exception ParseException; must be caught or declared to be thrown. setLenient(false) makes 30 February an error; without it, the old API quietly rolls it over into March.

P3Easy

The unchecked way to parse dates

Do the same conversion with LocalDate.parse and a DateTimeFormatter. It throws the unchecked DateTimeParseException: the code compiles without a try, but catch it anyway to show a message.

Input
"26-01-2025", "26/01/2025", "30-02-2025"
Expected output
26-01-2025 -> 2025-01-26
26/01/2025 -> Text '26/01/2025' could not be parsed at index 2
30-02-2025 -> Text '30-02-2025' could not be parsed: Invalid date 'FEBRUARY 30'
Show solution and explanation
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter f = DateTimeFormatter.ofPattern("dd-MM-uuuu")
                                               .withResolverStyle(ResolverStyle.STRICT);
        String[] dates = {"26-01-2025", "26/01/2025", "30-02-2025"};
        for (String d : dates) {
            try {
                System.out.println(d + " -> " + LocalDate.parse(d, f));
            } catch (DateTimeParseException e) {
                System.out.println(d + " -> " + e.getMessage());
            }
        }
    }
}
26-01-2025 -> 2025-01-26 26/01/2025 -> Text '26/01/2025' could not be parsed at index 2 30-02-2025 -> Text '30-02-2025' could not be parsed: Invalid date 'FEBRUARY 30'

How it works: the newer java.time API (Java 8+) chose an unchecked exception, so the compiler does not force a try. That makes calling it inside lambdas and streams easy, but it is now your job to remember that bad input throws. uuuu with STRICT rejects 30 February.

P4Medium

Is the class on the classpath?

Use Class.forName, which throws the checked ClassNotFoundException, to report which of these classes are available.

Input
"java.util.ArrayList", "com.example.PaymentPlugin", "java.time.LocalDate"
Expected output
Found:   ArrayList
Missing: com.example.PaymentPlugin
Found:   LocalDate
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        String[] names = {"java.util.ArrayList", "com.example.PaymentPlugin", "java.time.LocalDate"};
        for (String n : names) {
            try {
                Class<?> c = Class.forName(n);
                System.out.println("Found:   " + c.getSimpleName());
            } catch (ClassNotFoundException e) {
                System.out.println("Missing: " + e.getMessage());
            }
        }
    }
}
Found: ArrayList Missing: com.example.PaymentPlugin Found: LocalDate

How it works: loading a class by name can fail for reasons outside your code, such as a missing JAR file, so the API designers made it a checked exception. The message is the class name that could not be found.

P5Medium

Wrap it, keep the cause

Write parseOrFail(text) that converts a date and, on failure, throws an unchecked IllegalArgumentException that keeps the original ParseException as its cause. In main, print the message and the cause.

Input
"2025-13-01" with pattern yyyy-MM-dd (month 13)
Expected output
Message: Bad date: 2025-13-01
Cause:   java.text.ParseException: Unparseable date: "2025-13-01"
Show solution and explanation
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Main {
    static Date parseOrFail(String text) {
        SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
        f.setLenient(false);
        try {
            return f.parse(text);
        } catch (ParseException e) {
            throw new IllegalArgumentException("Bad date: " + text, e);
        }
    }

    public static void main(String[] args) {
        try {
            parseOrFail("2025-13-01");
        } catch (IllegalArgumentException e) {
            System.out.println("Message: " + e.getMessage());
            System.out.println("Cause:   " + e.getCause());
        }
    }
}
Message: Bad date: 2025-13-01 Cause: java.text.ParseException: Unparseable date: "2025-13-01"

How it works: the two-argument constructor (message, cause) links the new exception to the old one. parseOrFail no longer needs a throws clause because IllegalArgumentException is unchecked, yet nothing is lost: getCause() returns the original, and a printed stack trace would show it under “Caused by:”.

P6Medium

Validate a URL

new URI(text) throws the checked URISyntaxException for malformed addresses. Check three addresses and print the host, or the reason and position of the error.

Input
"https://shop.example.in/orders", "https://shop.example.in/search?q=red shoes", "http://my shop.in"
Expected output
OK, host shop.example.in
Bad: Illegal character in query at index 36
Bad: Illegal character in authority at index 9
Show solution and explanation
import java.net.URI;
import java.net.URISyntaxException;

public class Main {
    public static void main(String[] args) {
        String[] urls = {"https://shop.example.in/orders",
                         "https://shop.example.in/search?q=red shoes",
                         "http://my shop.in"};
        for (String u : urls) {
            try {
                URI uri = new URI(u);
                System.out.println("OK, host " + uri.getHost());
            } catch (URISyntaxException e) {
                System.out.println("Bad: " + e.getReason() + " at index " + e.getIndex());
            }
        }
    }
}
OK, host shop.example.in Bad: Illegal character in query at index 36 Bad: Illegal character in authority at index 9

How it works: a space is not allowed in a URI, so both failing addresses are rejected. The checked exception carries extra details, getReason() and getIndex(), which are handy for showing the user exactly where the problem is.

P7Medium

Declare it, handle it higher up

Instead of catching inside the helper, declare the checked exception with throws and catch it in main. Show that a method in between must also declare it.

Input
class names "java.util.HashMap" and "in.bank.Ledger"
Expected output
Plugin HashMap ready
Cannot load in.bank.Ledger
Show solution and explanation
public class Main {
    static String load(String name) throws ClassNotFoundException {
        return Class.forName(name).getSimpleName();
    }

    static String loadPlugin(String name) throws ClassNotFoundException {
        return "Plugin " + load(name);
    }

    public static void main(String[] args) {
        String[] names = {"java.util.HashMap", "in.bank.Ledger"};
        for (String n : names) {
            try {
                System.out.println(loadPlugin(n) + " ready");
            } catch (ClassNotFoundException e) {
                System.out.println("Cannot load " + e.getMessage());
            }
        }
    }
}
Plugin HashMap ready Cannot load in.bank.Ledger

How it works: the compiler checks every method: load can throw, so it declares throws; loadPlugin calls load and does not catch, so it must declare it too. main catches it, which ends the chain. Delete either throws clause and the program no longer compiles. Lesson 9.4 covers throws in detail.

P8Medium

Catch Exception, but not IOException?

Predict which of these blocks compile. A catch for a checked exception that the try body can never throw is a compile error, but catch (Exception e) is always allowed.

Input
a try body that only prints
Expected output
Block A
Block B
A and B compile; C would not
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        try {
            System.out.println("Block A");
        } catch (Exception e) {
            System.out.println("never runs");
        }
        try {
            System.out.println("Block B");
        } catch (RuntimeException e) {
            System.out.println("never runs");
        }
        // try { System.out.println("C"); } catch (java.io.IOException e) { }
        // ^ compile error: exception IOException is never thrown in body of corresponding try statement
        System.out.println("A and B compile; C would not");
    }
}
Block A Block B A and B compile; C would not

How it works: javac knows exactly which checked exceptions a try body can throw, so a catch for an impossible checked exception is flagged as unreachable. Exception and RuntimeException are different: unchecked exceptions can come from almost any line, so catching them is always allowed.

P9Business problem

Bank statement date check

A bank statement upload has transaction dates as text in dd-MM-yyyy. Count the valid and invalid dates, list the invalid ones, and report the earliest valid date.

Input
dates = {"03-04-2025", "31-04-2025", "01-04-2025", "1st April", "15-04-2025"}
Expected output
Invalid date: 31-04-2025
Invalid date: 1st April
3 valid, 2 invalid
Statement starts: 01-04-2025
Show solution and explanation
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        String[] dates = {"03-04-2025", "31-04-2025", "01-04-2025", "1st April", "15-04-2025"};
        SimpleDateFormat f = new SimpleDateFormat("dd-MM-yyyy");
        f.setLenient(false);
        Date earliest = null;
        String earliestText = "";
        int valid = 0;
        for (String d : dates) {
            try {
                Date parsed = f.parse(d);
                valid++;
                if (earliest == null || parsed.before(earliest)) {
                    earliest = parsed;
                    earliestText = d;
                }
            } catch (ParseException e) {
                System.out.println("Invalid date: " + d);
            }
        }
        System.out.println(valid + " valid, " + (dates.length - valid) + " invalid");
        System.out.println("Statement starts: " + earliestText);
    }
}
Invalid date: 31-04-2025 Invalid date: 1st April 3 valid, 2 invalid Statement starts: 01-04-2025

How it works:

  1. April has 30 days, so with setLenient(false) the date 31-04-2025 is rejected instead of silently becoming 1 May.
  2. The checked exception forces us to decide what to do with bad rows; here they are listed for the customer to correct.
  3. We compare Date objects with before, and keep the original text for printing so the output does not depend on the computer's time zone.
P10Business problem

Payment callback URL whitelist

A payment gateway only sends results to callback URLs that use https and whose host is pay.myshop.in. Validate each merchant setting and print ACCEPT or REJECT with a reason.

Input
"https://pay.myshop.in/cb", "http://pay.myshop.in/cb",
"https://evil.example.com/cb", "https://pay.myshop.in/c b"
Expected output
https://pay.myshop.in/cb -> ACCEPT
http://pay.myshop.in/cb -> REJECT (not https)
https://evil.example.com/cb -> REJECT (host evil.example.com)
https://pay.myshop.in/c b -> REJECT (Illegal character in path)
Show solution and explanation
import java.net.URI;
import java.net.URISyntaxException;

public class Main {
    static String check(String url) {
        try {
            URI u = new URI(url);
            if (!"https".equals(u.getScheme())) return "REJECT (not https)";
            if (!"pay.myshop.in".equals(u.getHost())) return "REJECT (host " + u.getHost() + ")";
            return "ACCEPT";
        } catch (URISyntaxException e) {
            return "REJECT (" + e.getReason() + ")";
        }
    }

    public static void main(String[] args) {
        String[] urls = {"https://pay.myshop.in/cb", "http://pay.myshop.in/cb",
                         "https://evil.example.com/cb", "https://pay.myshop.in/c b"};
        for (String u : urls) {
            System.out.println(u + " -> " + check(u));
        }
    }
}
https://pay.myshop.in/cb -> ACCEPT http://pay.myshop.in/cb -> REJECT (not https) https://evil.example.com/cb -> REJECT (host evil.example.com) https://pay.myshop.in/c b -> REJECT (Illegal character in path)

How it works:

  1. Malformed text throws the checked URISyntaxException, which check turns into a readable rejection reason.
  2. Well-formed URLs are then checked against the rules. Writing "https".equals(…) avoids a NullPointerException when a URL has no scheme.
  3. The method never throws; callers get a simple answer for each URL.
P11Business problem

Settings loader that fails clearly

An app's start-up settings include a launch date and a port. If any setting is broken, start-up must stop with one clear unchecked exception naming the setting, with the original problem attached as the cause.

Input
launchDate = "2025-02-29" (not a leap year), port = "8080"
Expected output
Start-up stopped: Setting launchDate is invalid
Because of: java.text.ParseException: Unparseable date: "2025-02-29"
Show solution and explanation
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class Main {
    static void validate(String launchDate, String port) {
        SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
        f.setLenient(false);
        try {
            f.parse(launchDate);
        } catch (ParseException e) {
            throw new IllegalStateException("Setting launchDate is invalid", e);
        }
        try {
            Integer.parseInt(port);
        } catch (NumberFormatException e) {
            throw new IllegalStateException("Setting port is invalid", e);
        }
        System.out.println("Settings OK");
    }

    public static void main(String[] args) {
        try {
            validate("2025-02-29", "8080");
        } catch (IllegalStateException e) {
            System.out.println("Start-up stopped: " + e.getMessage());
            System.out.println("Because of: " + e.getCause());
        }
    }
}
Start-up stopped: Setting launchDate is invalid Because of: java.text.ParseException: Unparseable date: "2025-02-29"

How it works:

  1. Two very different low-level problems (a checked ParseException, an unchecked NumberFormatException) are turned into the same kind of high-level exception with a message that names the setting.
  2. The original exception is kept as the cause, so the root reason, Unparseable date, is still visible.
  3. Failing fast at start-up is better than running with a wrong launch date.

Real-world scenario questions (asked in company interviews)

Checked vs unchecked is a design decision, and interviewers want to hear your reasons. Decide your answer first, then open the model answer.

SaaS · code reviewFresher

“I added throws Exception to make it compile”

A new teammate fixed an unreported exception IOException error by adding throws Exception to the method, then to its caller, then to every method up to main. It compiles now. What feedback do you give?

The interviewer is checking: do you know the purpose of the rule and the options for satisfying it?

Show a strong answer
  • throws Exception hides which problems can really happen, and it forces every caller to deal with “anything”.
  • Declare the specific type (throws IOException) only as far up as needed, and catch it at the first level that can do something useful: retry, fallback, or a clear error message.
  • If no caller can recover, wrap it once in an unchecked exception with the cause and a meaningful message.

Avoid: treating compile errors as obstacles to silence. The rule is asking for a plan.

Banking · API design1–3 years

Checked or unchecked for “insufficient funds”?

You are designing a transfer(from, to, amount) method for a banking library used by several teams. What exception types would you use for (a) insufficient balance, (b) a negative amount, (c) the core banking server being unreachable?

The interviewer is checking: can you match the exception kind to the situation and justify it?

Show a strong answer
  • (b) Negative amount: the caller's bug. Unchecked IllegalArgumentException; validate before calling.
  • (a) Insufficient balance: an expected business outcome every caller must handle. A custom exception (Lesson 9.5); checked makes it impossible to forget, unchecked is the more common modern style if the team documents it well. Some APIs return a result object instead.
  • (c) Server unreachable: an outside failure. Often a checked exception or a library-specific unchecked one; either way, callers need to know they can retry.
  • Whatever you choose, keep it consistent across the library and document it.

Avoid: one generic Exception for all three. Callers can't tell a bug from a retryable outage.

Analytics · data pipeline1–3 years

Checked exception inside a stream

Code reads a list of report file paths and calls paths.stream().map(p -> Files.readString(p)). It doesn't compile. A colleague suggests catching the exception inside the lambda and returning an empty string. Is that a good idea?

The interviewer is checking: do you understand why lambdas reject checked exceptions and how to wrap safely?

Show a strong answer
  • Function.apply declares no checked exceptions, so IOException can't escape the lambda.
  • Returning "" silently turns a missing report into an empty one: the totals will be wrong and nobody will notice.
  • Better: catch and rethrow new UncheckedIOException("Cannot read " + p, e), which keeps the cause and stops the pipeline; or collect failures explicitly and report them.

Avoid: “fixing” compile errors by swallowing exceptions.

Platform · debugging3+ years

The log says only “Load failed”

Production logs for a failing config loader show only java.lang.RuntimeException: Load failed and a short trace with no further detail. The code is catch (IOException e) { throw new RuntimeException("Load failed"); }. What went wrong and how do you fix it?

The interviewer is checking: do you know how to wrap exceptions without losing information?

Show a strong answer
  • The wrapper was created without the cause, so the original IOException (file name, permission denied, disk error) and its stack trace were thrown away.
  • Fix: throw new UncheckedIOException("Load failed for " + path, e). Now the trace shows a “Caused by:” section with the real reason.
  • Include the useful context (which file, which tenant) in the wrapper's message.

Avoid: new RuntimeException(e.getMessage()): it keeps only the text and loses the type and stack trace.