Every Java program eventually hits a situation it did not plan for: a file that is missing, a
number formatted wrongly, an array index that does not exist. When that happens, the JVM creates
an exception object and starts unwinding the call stack looking for code that can deal with it.
If nothing does, the thread dies with a stack trace. Exception handling is the set of keywords —
try, catch, finally, throw, throws — that let you intercept that object and respond
sensibly instead of crashing.
What happens when an exception is thrown
Picture a method call chain: main() calls readConfig(), which calls parsePort(). If
parsePort() receives the text "abc" and calls Integer.parseInt(), a
NumberFormatException object is created at that exact line. The JVM then asks: does
parsePort() have a matching catch? No? Then it abandons parsePort() and asks
readConfig(). Still no? It asks main(). This walk back up the chain is called stack
unwinding, and the printed record of it is the stack trace you see in the console.
Two things follow from this model. First, an exception is a normal Java object — it carries a message, a cause, and the stack trace. Second, you get to choose where in the chain to handle it. Handling it too early often means you cannot do anything useful; letting it travel to a layer that understands the business context usually produces better error messages.
The exception hierarchy at a glance
All throwable types descend from java.lang.Throwable, using plain
inheritance:
| Type | Parent | Should you catch it? |
|---|---|---|
Error (e.g. OutOfMemoryError) |
Throwable |
No — the JVM is in trouble |
Exception (e.g. IOException) |
Throwable |
Yes — checked, compiler forces handling |
RuntimeException (e.g. NullPointerException) |
Exception |
Usually fix the bug instead |
The split between checked and unchecked types matters enough that interviewers treat it as its own question — the full breakdown is in checked vs unchecked exceptions. For now: checked exceptions must be caught or declared, unchecked ones may simply propagate.
try, catch and finally in action
Here is the complete structure in one runnable program:
public class BankWithdrawal {
public static void main(String[] args) {
double balance = 5000.0;
double requested = 8000.0;
try {
if (requested > balance) {
throw new IllegalArgumentException(
"Requested " + requested + " but balance is " + balance);
}
balance -= requested;
System.out.println("Withdrawal successful");
} catch (IllegalArgumentException e) {
System.out.println("Withdrawal failed: " + e.getMessage());
} finally {
System.out.println("Closing ATM session for this customer");
}
System.out.println("Program continues normally");
}
}
Run it and notice the order of output: the catch block prints the failure, finally prints
the session message, and then the program continues past the whole construct. Change
requested to 3000.0 and finally still runs — that is the whole point. finally is where
cleanup belongs: closing connections, releasing locks, deleting temp files.
Common mistake: A common mistake beginners make is putting a
returninsidefinally. That return silently swallows any exception thrown intryorcatch, which makes bugs almost impossible to trace. Keepfinallyfor cleanup only, never for control flow.
Multiple catch blocks and multi-catch
When a try block can fail in more than one way, stack catch blocks from most specific to
most general. Since Java 7 you can also combine unrelated types in one block with |:
public class ConfigReader {
public static void main(String[] args) {
String[] input = {"8080"};
try {
String raw = input[1]; // may throw AIOOBE
int port = Integer.parseInt(raw); // may throw NFE
System.out.println("Port: " + port);
} catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
System.out.println("Bad config, using default port 8080");
System.out.println("Reason: " + e);
}
}
}
The multi-catch keeps the handling logic in one place when the recovery action is identical.
One rule the compiler enforces: a subclass catch cannot appear after its parent, because the
parent would already have caught everything — catch (Exception e) followed by
catch (IOException e) is a compile error.
Interview note: In a multi-catch block the variable
eis implicitlyfinal. You cannot reassign it, and its static type is the closest common superclass of the listed exceptions. This exact detail shows up in written screening tests.
throw vs throws: two keywords, two jobs
throw raises an exception right now. throws is a declaration on the method signature that
warns callers a checked exception may come out. Freshers mix these up constantly, so keep the
grammar straight: you throw an object, a method throws a type.
import java.io.FileNotFoundException;
public class LicenseChecker {
static void validate(String path) throws FileNotFoundException {
if (path == null || path.isBlank()) {
throw new FileNotFoundException("License path is empty");
}
System.out.println("License found at " + path);
}
public static void main(String[] args) {
try {
validate("");
} catch (FileNotFoundException e) {
System.out.println("Handled: " + e.getMessage());
}
}
}
Because FileNotFoundException is checked, validate() must either handle it or declare it
with throws — and once declared, main() is forced by the compiler to deal with it. That
compiler pressure is the defining feature of checked exceptions.
try-with-resources: the modern cleanup
Before Java 7, closing a file safely required a clumsy finally block with its own nested
try-catch. Try-with-resources fixes that: any object implementing AutoCloseable declared in
the parentheses is closed automatically, in reverse order, even when an exception is thrown.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class FirstLine {
public static void main(String[] args) {
String data = "line one\nline two";
try (BufferedReader reader = new BufferedReader(new StringReader(data))) {
System.out.println("Read: " + reader.readLine());
} catch (IOException e) {
System.out.println("Could not read: " + e.getMessage());
}
// reader is already closed here — no finally needed
}
}
Notice there is no explicit close() call anywhere. If both the body and the close() throw,
the body's exception wins and the close failure is attached as a suppressed exception — you
can inspect it with e.getSuppressed().
Pro tip: Whenever a class name suggests an external resource —
Connection,Stream,Reader,Socket— reach for try-with-resources first. In code reviews, a manualclose()in 2026-era Java is treated as a defect, not a style choice.
Writing your own custom exception
Business rules deserve their own exception types. A custom exception is just a class extending
Exception (checked) or RuntimeException (unchecked) with constructors that pass the message
and cause upward:
public class InsufficientBalanceException extends RuntimeException {
public InsufficientBalanceException(String message) {
super(message);
}
public InsufficientBalanceException(String message, Throwable cause) {
super(message, cause);
}
}
The second constructor matters more than beginners expect. When you catch a low-level exception and rethrow a business one, always pass the original as the cause — otherwise the real stack trace is lost and production debugging becomes guesswork. Wrapping without the cause is one of the most frequent findings when we review learner projects in the Java Full Stack program.
Best practices that come up in interviews
A few habits separate candidates who have written real Java from those who memorized syntax:
- Catch specific types.
catch (Exception e)at a low level hides programming errors like aNullPointerExceptionfrom a bad String operation. - Never leave a catch block empty. At minimum, log the exception. A silent catch turns a ten-minute bug into a ten-day one.
- Do not use exceptions for flow control. Checking
list.isEmpty()is dramatically cheaper than catchingIndexOutOfBoundsException, because filling in a stack trace is expensive. - Throw early, catch late. Validate inputs at the top of a method and throw immediately; handle exceptions in the layer that can actually explain the failure to a user or a log.
- Mind other threads. An uncaught exception kills only its own thread — in a
multithreaded program the rest of the
application keeps running, often in a half-broken state, unless you install an
UncaughtExceptionHandler.
Exception questions appear in almost every Java round, from fresher screenings to the scenario
drills in Java interview questions for 2 years experience.
If you can trace what happens to an exception object from throw to stack trace, and justify
when to use finally versus try-with-resources, you are ahead of most candidates at this level.
Frequently Asked Questions
What is the difference between throw and throws in Java?
Does finally always execute in Java?
Can a try block exist without a catch block?
What happens if an exception is not caught in Java?
Is it a good practice to catch Exception directly?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

