Last updated 19 September 2026. Default examples are mid-level Spring and Java 17–21. Junior and senior sit in labeled sections so the first screen is not a fresher dump.
Java exceptions are a type hierarchy and a control-flow tool. Interviews still open with checked versus unchecked, then jump to try-with-resources and why catching Exception is a smell. This hub is the exception head term. The harvest title for checked versus unchecked already lives on question 322; this page is the topic, not a second copy of that answer.
Junior
Throwable splits into Error and Exception. Error is for the JVM: OutOfMemoryError, StackOverflowError. You do not catch those in business code. Exception splits into checked exceptions (must declare or handle) and unchecked exceptions (RuntimeException and subclasses). checked means the compiler enforces a catch or throws. IOException is the usual example. NullPointerException is unchecked.
try-with-resources implements AutoCloseable and closes resources in reverse order, even when the body throws. The suppressed exceptions sit on the primary exception. A finally that returns will swallow an exception from the try; do not return from finally.
Never swallow. catch (Exception e) {} is a defect. Log with the throwable as the last argument. Wrap with a cause if you change the type. Do not lose the stack.
throws on the method signature is part of the contract for checked exceptions. Overriding methods cannot add new checked exceptions. They can throw narrower ones or unchecked ones.
Mid-level
Create your own exceptions when the caller can act. A checked exception is a demand that the caller decide. If nobody can recover, use an unchecked exception. Domain exceptions should be specific: AccountFrozenException, not Exception. Include identifiers in the message, not secrets.
Translation: Spring's DataAccessException hierarchy wraps SQLException so services do not import JDBC. Hibernate does similar work. Catch the translation, not the vendor type, at the boundary you chose.
try-with-resources versus explicit close in finally: the former is the default. If you must close in a different order or commit before close, say why. JDBC Connection in a Spring @Transactional method is not yours to close.
Performance: throwing is not free. The stack walk costs. Do not use exceptions for ordinary control flow in a hot loop. Optional and null policies are for missing values, not Exception.
Validation: Bean Validation throws ConstraintViolationException. Map it at the API boundary. Do not let it become a 500.
Senior
Senior exception design is about boundaries. A library should throw unchecked exceptions unless the caller has a realistic recovery. A public API documents error codes. Virtual threads do not change the type system; they change whether a blocked throw sits on a platform thread.
try-catch around a parallel stream or a CompletableFuture requires you to think about completion exceptions and which thread logs. ExceptionInInitializerError means a static initializer failed; the class is dead for the life of the JVM. Fix the initializer; do not catch that in request code.
Error handling versus Result types: Java 21 does not give you Rust Result. Sealed types plus a success/failure hierarchy can replace some checked exceptions inside a module. Do not invent that in every service. Consistency beats novelty.
Logging: one log per failure at the boundary. Nested services should throw, not log-and-throw, or you get four stack traces for one bug. Correlation ids belong on the log, not in the exception type name.
Probe yourself
Checked versus unchecked, in one sentence?
Checked exceptions must be declared or caught; unchecked extend RuntimeException and do not.
What does try-with-resources do with a second exception from close()?
It adds it as a suppressed exception on the primary throwable.
Why is catch (Exception e) { log; throw e; } often wrong in an inner layer?
The outer layer will log again. Prefer throw without log, or wrap once at the boundary.
Related questions on this topic are linked below. Read the full answer on the question URL; this hub does not repeat those answers.
Pitfalls interviewers still use
throws Exception on every method is not honesty; it is giving up. It forces every caller to handle a type they cannot diagnose. Narrow the type or use unchecked at the inner boundary.
printStackTrace() in a servlet is not logging. It goes to the container log without a request id. Use the logger. In tests, asserting on stderr is how you miss a swallowed error later.
Wrapping and losing the cause: new RuntimeException(e.getMessage()) drops the stack of the root. Pass e as the cause. Always.
Error versus Exception in a catch: catch (Throwable t) will catch OutOfMemoryError and then try to allocate a log line. You can make a bad situation worse. Catch Exception at business boundaries. Let Error kill the thread unless you are writing a container.
Checked exceptions on an override that the interface did not declare will not compile. That is the interview. Unchecked exceptions can appear anywhere; they are still part of the contract if you document them.
For the room: rewrite a method that throws Exception into one that throws a domain type or returns a result. Talk about who logs. That is senior hygiene, not trivia.
How to answer in the room
Define the hierarchy first: Throwable, Error, Exception, RuntimeException. Errors are JVM-level; you almost never catch them. Checked exceptions are Exception minus RuntimeException. Unchecked are RuntimeException and Error. The interview is whether you know who is forced to declare what.
When to use checked: a recoverable condition the caller can reasonably handle, like a missing optional file format the user can pick again. When to use unchecked: programming errors and domain failures that should bubble to a boundary. Spring's DataAccessException is unchecked for a reason. Do not invent a checked wrapper around it.
try-with-resources closes AutoCloseable in reverse order, even when the body throws. The close exception is suppressed if the body already threw. getSuppressed() is the follow-up. finally still exists; you rarely need it for streams if you used try-with-resources.
Never swallow. catch (Exception e) {} is a defect. Log at the boundary that owns the request, not in every layer. Wrap with the cause: new DomainException(e), not new DomainException(e.getMessage()). The stack of the root is the only useful artifact in production.
Override rules: a subclass method cannot add new checked exceptions. It can add unchecked. It can use a narrower checked type. This is the compile-time question they still ask.
API design: do not put throws Exception on a public method. It forces every caller into a useless catch. Prefer a domain type or an unchecked type plus a documented failure mode. In HTTP, map that type in advice to a status code you chose, not 500 for everything.
Assertions are not exceptions for production validation. assert is off by default. Use Objects.requireNonNull or an explicit check. Interviewers still mix these to see if you ship with -ea as a plan.
If they ask you to write a catch that logs and rethrows, keep the same type or wrap with a cause. Logging and swallowing is the defect. Logging and throwing without a cause is the other defect. Production needs one stack that reaches the line that failed, not a new stack that starts in your catch.