The exception hierarchy, checked vs unchecked, try/catch/finally edge cases (return, System.exit, multi-catch), finally vs finalize, generics and type erasure, enums, and reflection.
Published September 25, 2026
Exception questions tend to escalate quickly: "what's a checked exception?" becomes "what happens if both try and finally return?". Know the hierarchy, the edge cases, and the modern idioms (try-with-resources, multi-catch).
Short answer: An exception is an object representing an abnormal event that interrupts normal flow: a missing file, invalid input, a null reference. When one is thrown, the JVM unwinds the call stack until it finds a matching catch. If nothing catches it, the thread terminates and prints the stack trace.
Key points to cover:
Throwable → Error (JVM problems) and Exception → RuntimeException (unchecked) plus the other checked exceptions.Throwable
├── Error (OutOfMemoryError, StackOverflowError) — don't catch
└── Exception (checked: IOException, SQLException)
└── RuntimeException (unchecked: NullPointerException, IllegalArgumentException)
Learn it in depth → Exception Handling
Short answer: Checked exceptions (subclasses of Exception, but not of RuntimeException) are verified at compile time. You must either catch them or declare them with throws. Unchecked exceptions (RuntimeException, Error and their subclasses) don't need to be declared or caught.
| Checked | Unchecked | |
|---|---|---|
| Examples | IOException, SQLException, InterruptedException | NullPointerException, IllegalArgumentException, ArithmeticException |
| Compiler enforces handling | Yes | No |
| Typically means | A recoverable external condition | A programming bug, or a violated precondition |
Key points to cover:
SQLException into its unchecked DataAccessException hierarchy.Learn it in depth → Exception Handling
try, catch and finally?Short answer:
try wraps the code that might throw.catch handles a specific exception type.finally always runs afterwards, whether or not an exception occurred, and is used for cleanup.Connection con = null;
try {
con = dataSource.getConnection();
// work
} catch (SQLException e) {
throw new OrderPersistenceException("could not save order", e); // wrap, keeping the cause
} finally {
if (con != null) try { con.close(); } catch (SQLException ignored) { }
}
// Modern equivalent: try-with-resources closes automatically, in reverse order
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(SQL)) {
ps.executeUpdate();
}
Key points to cover:
AutoCloseable) to manual finally cleanup. It also preserves "suppressed" exceptions thrown by close().try or catch executes a return, does finally still run?Short answer: Yes. finally runs after the return value is computed but before control goes back to the caller.
static int demo() {
int x = 1;
try { return x; } // the return value 1 is saved here
finally { x = 99; } // runs, but doesn't change the saved value
} // demo() returns 1
static int bad() {
try { throw new RuntimeException("lost"); }
finally { return 2; } // a return in finally SWALLOWS the exception, and returns 2
}
Common trap: a return (or throw) inside finally silently discards any exception or return value from try. Never return from finally.
try without catch?Short answer: Yes. try–finally (no catch) runs cleanup while letting the exception propagate to the caller. try-with-resources can also stand alone, with no catch and no finally.
lock.lock();
try {
updateSharedState();
} finally {
lock.unlock(); // always released, even if updateSharedState() throws
}
Short answer: A try block itself costs essentially nothing. Throwing an exception is what's expensive, mainly because the stack trace is captured, and unwinding the stack takes time. So exceptions are fine for exceptional situations, but they shouldn't be used for normal control flow.
Key points to cover:
if (map.containsKey(k))), or return Optional.Throwable(msg, cause, false, false) constructor.finally block not execute?Short answer:
System.exit() in try or catch, Runtime.halt(), a JVM crash, or the process being killed (kill -9, power loss).try block never finishes, because of an infinite loop or a deadlock.Key points to cover:
System.exit() runs shutdown hooks, but not pending finally blocks.try have multiple finally blocks? How do you handle several exceptions in one catch?Short answer: No. Each try can have at most one finally (but many catch blocks). To handle several exception types the same way, use multi-catch (Java 7+):
try {
importFile(path);
} catch (IOException | ParseException e) { // e is effectively final here
log.warn("import failed for {}", path, e);
}
Key points to cover:
IOException | Exception is a compile error, because it's redundant).Throwable, Exception and Error?Short answer: Throwable is the root of everything that can be thrown. Exception represents conditions an application might reasonably handle. Error represents serious JVM-level problems (OutOfMemoryError, StackOverflowError, NoClassDefFoundError) that applications normally shouldn't catch.
Key points to cover:
Throwable or Error broadly hides fatal problems. The exception is top-level logging in thread pools or frameworks.finally and finalize()?Short answer: finally is a language block that runs deterministically after try/catch. finalize() was a method the garbage collector might call before reclaiming an object. It's deprecated, unpredictable, and it may never run at all: for example, if the object is never collected, or the JVM exits first.
Learn it in depth → Garbage Collection Fundamentals
Short answer: Generics let classes, interfaces and methods take type parameters (List<String>, Map<K, V>, <T> T first(List<T>)). They give compile-time type safety, which means no casts and no runtime ClassCastException from putting the wrong type in a collection, and they let one implementation serve many types.
List raw = new ArrayList(); // pre-Java 5: anything goes
raw.add("x"); Integer n = (Integer) raw.get(0); // ClassCastException at runtime
List<String> names = new ArrayList<>();
names.add("Asha");
// names.add(42); // compile error: caught early
String first = names.get(0); // no cast needed
Key points to cover:
List<String> becomes List in bytecode). That's why you can't do new T(), can't use instanceof List<String>, and can't create a new T[] array.<T extends Comparable<T>>. Wildcards: ? extends T for reading (producer), and ? super T for writing (consumer), known as PECS.Learn it in depth → Generics
Short answer: An enum is a type with a fixed set of named constants, such as OrderStatus { PLACED, PAID, SHIPPED, DELIVERED }. Java enums are full classes: they can have fields, constructors, methods, and even per-constant behaviour.
public enum OrderStatus {
PLACED(false), PAID(false), SHIPPED(false), DELIVERED(true), CANCELLED(true);
private final boolean terminal;
OrderStatus(boolean terminal) { this.terminal = terminal; }
public boolean isTerminal() { return terminal; }
}
Key points to cover:
int or String), and they work in switch (with exhaustiveness checks in switch expressions).final, and each constant is a singleton. EnumMap and EnumSet are very fast collections keyed by enums.@Enumerated(EnumType.STRING)) in databases, never the ordinal. Reordering the constants would silently corrupt the data.Short answer: Reflection (java.lang.reflect) lets code inspect and use classes at runtime: list fields, methods, constructors and annotations, create instances, call methods, and read or write fields, even private ones, when access is allowed.
Class<?> type = Class.forName("com.shop.Order");
Object order = type.getDeclaredConstructor().newInstance();
Method m = type.getMethod("setStatus", String.class);
m.invoke(order, "PAID");
Key points to cover:
@Test methods).Q: What is exception chaining?
A: Wrapping a low-level exception as the cause of a higher-level one (new ServiceException("…", e)), so the root cause and its stack trace aren't lost. Always pass the cause when rethrowing.
Q: How do you create a custom exception?
A: Extend RuntimeException (or Exception if callers must handle it), and provide constructors that take a message and a cause: class InsufficientStockException extends RuntimeException { … }.
Q: Is throw different from throws?
A: throw actually throws an exception object inside a method body. throws in a method signature declares which checked exceptions the method may propagate.
Q: What is NoClassDefFoundError vs ClassNotFoundException?
A: ClassNotFoundException is a checked exception from explicit dynamic loading (Class.forName) when the class isn't found. NoClassDefFoundError is an Error raised when a class that was present at compile time is missing at runtime, or failed its static initialisation.