Exception Handling
In the real world things go wrong — files vanish, input is garbage, networks drop. Learn to catch those failures and keep your program running instead of crashing.
Learn Exception Handling in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
This lesson builds on a few earlier ones. You should be comfortable with:
An exception is like a fire alarm in a building. When something goes badly wrong, you don't want everyone to freeze and the building to collapse — you want an emergency procedure that takes over.
throw = pulling the alarm: "something is wrong, stop normal work now."
try = the part of the building wired to the alarm system.
catch = the trained response: evacuate, call for help, recover.
finally = locking up afterwards — it happens whether or not there was a fire.
Without this, one bad input would crash the whole program. With it, you detect the problem, respond, and carry on.
You put risky code — anything that might fail — inside a try block. If it throws an exception, Java jumps straight to a matching catch block and runs your recovery code. The finally block runs afterwards no matter what , so it's where cleanup goes.
Every exception object carries a message you can read with e.getMessage() and a type you can read with e.getClass().getSimpleName() . The next example uses both.
Every error in Java is an object, and they all descend from one root class, Throwable . The shape of this tree decides what you must catch versus what's optional:
Error means the JVM itself is broken (out of memory, infinite recursion). You normally let these crash — there's nothing useful you can do.
Checked exceptions extend Exception but not RuntimeException . The compiler refuses to build until you handle them. They model recoverable situations a caller should plan for.
Unchecked exceptions extend RuntimeException . They usually mean a bug in your code — a null you forgot to check, an index off the end of an array.
throw (no s ) is a statement — it actually raises an exception right now. throws (with s ) is part of a method signature — it warns callers that this method might raise a checked exception.
When two different exceptions need the same handling, don't write two near-identical catch blocks. Join the types with a single pipe | :
Order still matters when types are related: a more specific type must come before its parent, or you'll get a compile error for unreachable code.
A custom exception is just a class that extends an existing exception. Extend Exception for a checked one (callers must handle it) or RuntimeException for an unchecked one. The payoff: a name that says exactly what went wrong, plus room for extra data.
Now a caller can write catch (InsufficientFundsException e) and read e.getDeficit() — far clearer than catching a generic RuntimeException and guessing.
Analogy: it's a self-closing door — you walk through and it shuts behind you. Declare a resource in the try (...) parentheses and Java calls its close() method automatically when the block ends, even if an exception is thrown. The resource just has to implement AutoCloseable .
You can also chain exceptions: when you catch a low-level error and rethrow a higher-level one, pass the original as the second argument so the cause isn't lost. Read it back with e.getCause() . The next example shows both.
Fill in the three blanks so the program catches a bad array index and still runs its cleanup line. The expected output is in the comments — match it exactly.
Complete the custom exception so it's checked , then raise it with the right keyword. The throws clause and the catch are already wired up for you.
Now with the scaffolding removed. Read the brief in the comments and write the method yourself — only the outline is given. Use a try / catch (NumberFormatException e) to fall back to a default when the text isn't a number.
Fix: at minimum log it, or rethrow it. A silent failure is the hardest bug to find: catch (IOException e) {' log.error("read failed", e); '}
Fix: catch the most specific type you can actually recover from, and let real bugs propagate: catch (FileNotFoundException e) before catch (IOException e) .
❌ Leaking resources without try-with-resources
Fix: declare the resource in try (...) so close() always runs: try (var conn = openConnection()) {' conn.use(); '}
Fix: that message means a checked exception escaped unhandled. Either wrap the call in try/catch or add throws IOException to the method. Unchecked ( RuntimeException ) types never produce this error.
Don't replace an if with a try/catch. Exceptions are far slower than a plain conditional and they hide your logic — reserve them for genuinely exceptional situations.
You can now write robust Java that survives the unexpected — wrap risky code in try / catch / finally , read the Throwable hierarchy, tell checked from unchecked, raise errors with throw and declare them with throws , collapse handlers with multi-catch, auto-close resources with try-with-resources, and design custom exceptions that name your failures.
Next up: Collections Framework — work with ArrayList , HashMap , HashSet , and the data structures real programs are built on.
Practice quiz
Which block runs whether or not an exception was thrown?
- try
- catch
- finally
- throw
Answer: finally. The finally block always runs — it is where cleanup goes.
What is the difference between 'throw' and 'throws'?
- They are identical
- throw raises an exception now; throws declares a checked exception in a method signature
- throws raises an exception; throw declares it
- throw is for errors, throws is for warnings
Answer: throw raises an exception now; throws declares a checked exception in a method signature. throw is a statement that raises an exception; throws declares a checked exception to callers.
A checked exception extends which class (but not RuntimeException)?
- Throwable
- Error
- Exception
- Object
Answer: Exception. Checked exceptions extend Exception but not RuntimeException, so the compiler forces handling.
Which is the root class of the whole exception hierarchy?
- Exception
- Error
- Throwable
- RuntimeException
Answer: Throwable. Everything throwable descends from Throwable.
To create an UNCHECKED custom exception, you extend...
- Exception
- RuntimeException
- Error
- Throwable
Answer: RuntimeException. Extending RuntimeException makes an unchecked exception — no throws clause needed.
What does multi-catch 'catch (NumberFormatException | NullPointerException e)' do?
- Catches both types with the same handler
- Catches only the first type
- Is a compile error
- Catches every exception
Answer: Catches both types with the same handler. The | joins types so one block handles either exception the same way.
Which interface must a class implement to be used in try-with-resources?
- Closeable only
- AutoCloseable
- Disposable
- Resource
Answer: AutoCloseable. A resource in try(...) must implement AutoCloseable; its close() is called automatically.
Do you need a 'throws' clause for an unchecked (RuntimeException) exception?
- Yes, always
- No, it can be thrown without declaration
- Only in main()
- Only if caught
Answer: No, it can be thrown without declaration. Only checked exceptions require throws; unchecked ones can be thrown freely.
After try-with-resources, when is the resource's close() method called?
- Only if no exception is thrown
- Automatically when the block ends, even on an exception
- Never automatically
- Only inside finally
Answer: Automatically when the block ends, even on an exception. close() runs automatically at the end of the block whether it ends normally or via an exception.
How do you read the original cause of a wrapped/chained exception?
- e.getMessage()
- e.getCause()
- e.getClass()
- e.getOrigin()
Answer: e.getCause(). getCause() returns the original exception passed as the chaining cause.