Java try-catch-finally — The Silent Connection Leak Pattern
Connection pool leaks over 4-6 hours until exhaustion because finally block discards original exception.
20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- try-catch-finally separates happy path from error handling, guaranteeing cleanup via finally
- Exception immediately exits the try block and jumps to the first matching catch — remaining try lines are skipped
- finally runs in every exit path — normal completion, exception, return, break, continue — except Runtime.halt(), os-level SIGKILL, or an infinite loop that never exits
- Multiple catch blocks must be ordered most-specific to most-general — catching a supertype before a subtype is a compile error for checked exceptions
- Performance: In modern HotSpot (Java 11+), a try block with no exception thrown has near-zero overhead on hot paths — the JIT eliminates it. The real cost is on actual throw: stack trace generation walks the entire call stack. For high-frequency expected conditions, override fillInStackTrace() in your custom exception to skip that cost
- Production mistake: a return statement inside finally silently replaces whatever try or catch was returning — no warning, no error, wrong answer
Imagine you're a surgeon following a pre-op checklist. You TRY to complete the procedure exactly as planned. If something unexpected happens mid-surgery — the patient reacts to anaesthesia, an instrument fails — you CATCH that specific problem and follow the contingency protocol for it. No matter what happens — successful procedure or emergency response — you FINALLY complete the post-op checklist: remove all instruments, close, hand over to recovery. Skipping that checklist is not optional, regardless of how the surgery went. That's try-catch-finally: attempt the risky operation, handle each specific failure mode with its own response, and always run the mandatory cleanup — no exceptions to the cleanup rule, even when there are exceptions to everything else.
Every program that talks to a database, reads a file, or calls an external API is making a promise it cannot always keep. Networks drop. Files get deleted. Users type letters where numbers should go. Java's exception handling is not a safety net you bolt on at the end — it is a first-class design decision that separates production-quality code from code that only works on your laptop.
Before structured exception handling existed, error handling meant checking return codes after every single operation — if (result == -1) doSomething() — scattered everywhere, easy to forget, and nearly impossible to read under any complexity. Java's try-catch-finally lets you separate the happy path logic from the something went wrong logic, keeping both readable and in their proper place. The finally block solves a separate but equally critical problem: making sure resources like database connections and file handles are released even when your code fails unexpectedly.
This article goes further than syntax. You'll understand exactly how execution flows through a try-catch-finally block including the surprising edge cases that trip up experienced engineers, when to catch a specific exception versus a broad one, how to write resource cleanup that cannot leak connections, and how the language has evolved through Java 21 to make exception handling both safer and more expressive. You'll also walk out with ready answers for the tricky flow-control questions that interviews reliably produce.
How try-catch-finally Actually Works in Java
try-catch-finally is Java's structured exception handling mechanism. The try block monitors code for exceptions; catch blocks handle specific exception types; finally always executes after try (or catch) completes, regardless of whether an exception was thrown. This is guaranteed by the JVM specification — finally runs even if the catch block throws or a return statement is executed.
Key properties: finally executes after the try block exits, but before control returns to the caller. If both try and finally have return statements, finally's return overrides the try's return — a common source of bugs. The JVM ensures finally runs even if an exception propagates, but not if the JVM crashes (System.exit, fatal error) or the thread is killed.
Use finally to release heavyweight resources: database connections, file handles, network sockets. Without finally, an exception in the try block skips the cleanup code, leaking resources until garbage collection or system exhaustion. In production, this manifests as connection pool exhaustion or file descriptor leaks, often after a spike in errors.
System.exit(), fatal JVM errors, or thread termination via Thread.stop() will skip finally — never rely on it for critical cleanup in those paths.How Execution Actually Flows Through try-catch-finally
Most developers learn the happy path first: try runs, nothing breaks, finally runs, done. The real value comes from understanding every path through the block — and there are more than you'd expect.
When an exception is thrown inside a try block, Java immediately stops executing that block. It does not finish the remaining lines. It jumps to the first catch block whose declared type matches the thrown exception's type, checking each catch clause top-to-bottom. If no catch matches, the exception propagates up the call stack — but finally still runs on the way out, before the exception reaches any outer catch in the caller.
That ordering detail matters in practice: if your finally block closes a resource, the caller's catch block cannot assume that resource is still open when it handles the exception.
finally runs in every scenario except three: Runtime.getRuntime().halt() which is a hard JVM shutdown bypassing all cleanup, an os-level SIGKILL which the JVM cannot intercept, and an infinite loop or deadlock inside the try block itself which prevents the block from ever exiting. System.exit() internally calls Runtime.halt() — so it also bypasses finally. For everything else — normal completion, caught exception, uncaught exception, return statement, break, continue — finally runs before any of those operations complete.
You can also stack multiple catch blocks. Java checks them in declaration order and uses the first match. This means you must always declare more specific exception types before more general ones. For checked exceptions, catching a supertype before a subtype is a compile error — the compiler can prove the specific branch is unreachable. For unchecked exceptions the compiler may only produce a warning depending on your toolchain, so do not rely on the compiler to catch this for you.
Runtime.halt(), SIGKILL, or a try block that never exits.Catching Multiple Exceptions: Specific Before General
Real applications throw more than one kind of exception. A method that reads a configuration file could fail because the file does not exist (NoSuchFileException), because the process lacks permission to read it (AccessDeniedException), or because the data inside cannot be parsed (NumberFormatException). Each failure mode deserves a different response — they are not the same problem and should not be treated as one.
Java lets you stack multiple catch blocks to handle each case differently. The ordering rule is firm: always declare your catch blocks from most specific to most general. NoSuchFileException is a subclass of IOException, which is a subclass of Exception. If you declare catch (IOException e) before catch (NoSuchFileException e), the compiler rejects it for checked exceptions — the specific branch is provably unreachable. For unchecked exceptions, some compiler configurations only warn rather than error, so do not rely on the toolchain to catch ordering mistakes in all cases.
Since Java 7, you can use multi-catch with the pipe operator to handle multiple unrelated exception types with identical recovery logic, without duplicating code. The catch variable is implicitly final in multi-catch — you cannot reassign it. The types listed must not be in a subtype relationship with each other; the compiler rejects 'Alternatives in a multi-catch statement cannot be related by subclassing' because one branch would always be unreachable.
The limit that experienced engineers push against: catching Exception or Throwable as the sole catch block. Throwable includes Error subclasses like OutOfMemoryError and StackOverflowError — conditions from which the JVM state is typically corrupt and recovery is impossible. Catching them and continuing as if nothing happened is how you get a JVM that appears to run but produces silently wrong results. Let Error propagate to a top-level UncaughtExceptionHandler that can log, alert, and let the process die cleanly.
Thread.setDefaultUncaughtExceptionHandler() at startup to handle those at the boundary.Exception Chaining: Preserving the Root Cause Across Layers
Most codebases have at least three layers: a repository or data layer, a service layer, and an API or presentation layer. Exceptions that originate in the data layer — a JDBC SQLException, a network SocketTimeoutException — are low-level details that the service layer should translate into domain-appropriate exceptions before surfacing them upward. The problem is that translation, done carelessly, permanently destroys the original root cause.
The wrong pattern looks like this: catch the low-level exception, construct a new high-level exception using only a message string, and throw the new one. The original exception is gone. When this hits production and you're trying to find out which SQL statement failed, or which network host timed out, there is no stack trace to follow — just a generic message.
The right pattern is exception chaining. Java's Throwable constructors accept a cause argument: throw new ServiceException("User lookup failed", e). This binds the original exception as getCause() on the new one. Every standard logging framework — SLF4J, Log4j2, JUL — automatically prints the full cause chain when you pass the exception to the logger. The developer reading the log sees both the domain-level failure and the exact low-level cause in a single traceback.
Custom exception classes are the other half of this story. A generic RuntimeException with a message string tells the caller nothing they can act on programmatically. A UserNotFoundException with a userId field, or a PaymentProcessingException with a statusCode and retryable boolean, gives the caller structured data to make decisions with. The exception becomes part of the API contract, not just a message carrier.
Java 17 sealed classes extend this further. You can define a sealed exception hierarchy where the compiler knows exhaustively which subtypes exist — relevant when paired with pattern matching in catch blocks (Java 21+).
finally for Resource Cleanup — and Why try-with-resources Does It Better
The classic use of finally is closing resources: database connections, file streams, network sockets. If you open it, you must close it — even if an exception fires halfway through. Before Java 7, finally was the only tool for this job, and it had a flaw that caused real production incidents.
The flaw: if the cleanup code inside finally itself throws an exception, that new exception becomes the active one and permanently discards the original. The exception that told you what actually went wrong — which query failed, which network call timed out — is gone. You see only the close() failure, which is usually a secondary symptom.
Java 7's try-with-resources solves this cleanly. Any object that implements AutoCloseable can be declared in the try's parentheses. Java guarantees close() is called automatically when the block exits — whether normally or via exception — and if both the main code and the close() call throw, Java keeps the original as the primary exception and attaches the close() exception as a suppressed exception accessible via getSuppressed(). Nothing is lost.
Multiple resources in a single try-with-resources are declared with semicolons. They close in reverse order of declaration — the last declared closes first — which mirrors the stack discipline you would use manually. This reverse-order close prevents a common bug where closing an outer resource before an inner one leaves the inner resource in an indeterminate state.
For resources that do not implement AutoCloseable — legacy objects you cannot modify, shutdown hooks, metrics flush calls — finally remains the right tool. The key discipline: keep finally bodies to a single, robust cleanup action. If the cleanup itself needs error handling, wrap it in its own try-catch inside finally, not by letting it throw and discard your primary exception.
close() call throws an exception, the remaining close() calls are skipped, and one or more resources leak permanently. try-with-resources eliminates this entire class of bug — all declared resources are closed regardless of whether earlier close() calls throw.close() failures as suppressed — nothing is silently discarded.The finally Gotcha: When finally Overrides a Return Value
Here is the one that trips up experienced developers in interviews and production code alike: what happens when both the try block and the finally block contain a return statement?
Java's answer is unambiguous but surprising: finally wins, always. When a try block reaches a return, the return value is computed and held in a temporary location. Then finally executes. If finally also returns, that new value overwrites the held one and becomes what the caller receives. The try block's return value is silently discarded. No warning. No compiler error. Just the wrong answer.
The same applies to exceptions. If try throws an exception and finally also throws a different exception, the original exception is permanently discarded and the finally exception propagates instead. Unlike try-with-resources, which attaches close exceptions as suppressed, a throw inside a plain finally block performs a hard replacement — the original cause is gone entirely.
This behaviour exists because finally is specified to have the last word before a method completes — it was designed to ensure cleanup happens. But it means that any side-effecting code in finally, including a return or throw, changes the observable outcome of the method.
The practical rule is absolute: never put return, throw, break, or continue inside a finally block. Use finally exclusively for side-effect cleanup — closing resources, decrementing counters, resetting state. If you need to compute and return a value that depends on whether cleanup succeeded, do that computation after the try-finally block, not inside it.
Runtime.halt() is called, the JVM crashes at the OS level, or the try block never exits due to an infinite loop or deadlock. When they ask 'what if both try and finally have a return statement?' — finally wins and the try return is permanently discarded. When they ask 'what if try throws and finally also throws?' — the finally exception wins and the original exception is gone, with no suppressed chain. Knowing these three distinctions precisely is what separates a careful answer from a vague one.The throws Keyword: Declaring, Delegating, and Designing Exception Contracts
Every discussion of try-catch is incomplete without covering throws — the mechanism by which a method declares it may raise a checked exception without handling it internally. Understanding throws is what lets you design exception handling as a deliberate API contract rather than a series of reactive patches.
Checked exceptions are exceptions that are subclasses of Exception but not RuntimeException. The Java compiler requires that any method which may throw a checked exception either handles it with try-catch or declares it with throws in the method signature. This requirement exists to force an explicit decision at every layer: either handle it here, or acknowledge to the caller that they must handle it.
Unchecked exceptions — RuntimeException and its subclasses, plus Error — carry no such requirement. They can propagate freely without appearing in any throws clause. This is why NullPointerException, IllegalArgumentException, and similar programming-error exceptions do not clutter method signatures.
The design decision of whether to make a custom exception checked or unchecked is worth thinking through explicitly. Checked exceptions communicate: 'this is a recoverable condition that a reasonable caller might handle — I am forcing them to make a decision.' Unchecked exceptions communicate: 'this is either a programming error or an unrecoverable condition — there is nothing useful the immediate caller can do.' Most modern Java codebases, including Spring, have moved toward unchecked exceptions for domain errors precisely because checked exceptions tend to accumulate in throws clauses and get caught and silently swallowed just to satisfy the compiler.
The throws clause is also documentation. A method declared as throws SQLException tells anyone reading the signature that database errors are a possible outcome they need to plan for. A method that wraps SQLExceptions in an unchecked RuntimeException and declares nothing is hiding that information — the caller finds out at runtime instead of at compile time.
Exception Handling in Java 21: Virtual Threads and Structured Concurrency
Java 21 introduced two features that change how exception handling works in concurrent code: virtual threads and structured concurrency via StructuredTaskScope. If your codebase runs on Java 21 or later — and by 2026 most active codebases should — these are not optional reading.
Virtual threads are lightweight threads managed by the JVM rather than the OS. From an exception handling standpoint, they behave like platform threads: uncaught exceptions are delivered to the thread's UncaughtExceptionHandler. The key difference is scale — you might run tens of thousands of virtual threads simultaneously. A global UncaughtExceptionHandler that was previously a backstop for rare cases is now a critical observability component, because virtual thread exceptions that go unhandled are easy to miss at scale.
StructuredTaskScope is the more important change for exception handling design. It provides a structured approach to concurrent tasks where the scope's lifecycle guarantees that all forked tasks complete — successfully or via exception — before the scope closes. This eliminates the classic concurrent bug where a task failure is silently ignored because nobody checked its Future.
The two built-in policies cover the most common production patterns: ShutdownOnFailure cancels all remaining tasks if any one fails and re-raises the first exception via throwIfFailed(), and ShutdownOnSuccess returns as soon as any task succeeds and cancels the rest. Both give you clear exception semantics without manually managing Future.get() and its checked ExecutionException unwrapping.
Future.get(). Both have a shared problem: exceptions are wrapped in ExecutionException and it is easy to forget to call get() at all, silently ignoring task failures. StructuredTaskScope makes failure handling structural — the scope cannot close until all tasks have completed, and throwIfFailed() makes exception propagation explicit rather than opt-in. For any new Java 21+ concurrent code, StructuredTaskScope should be the default, not an advanced option.Future.get() is called without handling ExecutionException explicitly, or where futures are submitted but never waited on. These are the silent failure points. StructuredTaskScope makes them compile-time visible rather than runtime surprises.Best Practices for Exception Handling in Production
Beyond the syntax, exception handling in production is about three things: preserving context so the next engineer can diagnose quickly, avoiding silent failures that corrupt data without any trace, and understanding where the real performance cost lives.
Preserve context at every layer. Log the full exception object, not just e.getMessage(). The message alone is often useless — 'Connection refused' tells you nothing about which host, which port, which request was in flight. Pass the exception as the final argument to your SLF4J logger and the full stack trace, cause chain, and suppressed exceptions all appear automatically.
Fail fast rather than return a default. If a catch block cannot fully recover from an exception, rethrowing is almost always better than returning null, -1, or an empty list. A NullPointerException three method calls downstream from a silently swallowed exception is one of the hardest bugs to diagnose in production — the connection between cause and symptom is invisible.
Understand where the performance cost actually lives. In modern HotSpot JVM (Java 11+), a try block with no exception thrown has near-zero overhead on the JIT-compiled hot path — the JIT eliminates the try machinery entirely in tight loops. The expensive part is throwing: generating a stack trace walks the entire call stack and allocates. For genuinely high-frequency expected conditions — cache misses, validation failures in a hot parse loop — override fillInStackTrace() in your custom exception to return this without generating a trace. You keep the type information and can still catch it, but at a fraction of the cost.
Do not use exceptions for control flow. A method that throws NotFoundException to signal 'no record found' in a normal query path is using exceptions for flow control — the equivalent of using a goto. Return an Optional, a null with documentation, or a Result type. Reserve exceptions for genuinely exceptional conditions.
Thread.setDefaultUncaughtExceptionHandler() at startup. In virtual-thread-heavy Java 21+ applications: set the UncaughtExceptionHandler on the virtual thread factory. These are not defensive additions — they are the final catch in your exception handling architecture, and without them any exception that slips through goes silently to stderr.The 'Basics' That Burn Juniors: try, catch, finally Syntax & Why Only try Is Mandatory
Every Java developer writes try-catch. Most don't understand the grammar until it bites them in production. Here's the hard truth: only the try block is mandatory. You can have zero catch blocks, zero finally blocks, or both. But if you write a catch, it must be paired with a try. A finally without a try is a compile error.
The real kicker? A try block with no catch and no finally compiles, but it's useless garbage. If an exception fires inside that lone try, it propagates up the stack unhandled — same as if you'd never written the try. The only legitimate reason to write try-finally without catch is when you guarantee cleanup must happen regardless of success, and you want the exception to keep propagating. Resource cleanup was the classic use case before Java 7, but try-with-resources murdered that pattern.
This isn't syntax trivia. Ask yourself: when you write a try block, what's your intent? If you aren't catching or cleaning up, you're wasting keystrokes and misleading your team.
How an Exception Floats — And Why Stack Traces Lie to You
When an exception fires inside a try block, Java doesn't just stop there and throw its hands up. It performs a methodical three-step dance. First, execution of the try block halts immediately — any code after the offending line never runs. Second, Java walks up the call stack looking for a matching catch block in the current method. If it finds one, the exception is handled there, and the finally block (if any) runs before control passes to the rest of the method. If no catch matches, the finally block still runs, but then the exception propagates to the caller, the caller's try-catch, and so on up the stack.
Here's where juniors get burned: the stack trace shows the throw point, but it doesn't tell you which catch actually ate it. If you have nested try-catch blocks, the first matching catch up the chain handles it. That means a generic Exception catch in an outer block can swallow a specific RuntimeException from an inner block. The stack trace will point to the inner throw, but the error message in logs won't show you which layer consumed it. Always log the full exception object, not just getMessage().
Another gotcha: if a finally block throws an exception, it masks the original exception from the try or catch. Java 7's try-with-resources handles this with suppressed exceptions, but with raw try-catch-finally, the finally exception wins and the original is lost. That's a debugging nightmare.
Overview
Exception handling in Java is a structured mechanism for managing runtime anomalies without crashing the application. At its core, the try-catch-finally construct allows you to isolate risky code, respond to failures, and release resources in a predictable order. The try block monitors code that may throw exceptions; catch blocks define how to respond to specific exception types; and finally ensures cleanup executes regardless of outcome. Understanding the execution flow is critical: when an exception occurs, control jumps immediately to the matching catch block, skipping any remaining try code. The finally block always runs after catch completes, unless the JVM terminates abruptly. This guarantee makes finally ideal for closing files or database connections, but developers often misuse it by masking return values or ignoring resource leaks. The language evolved to address these pitfalls with try-with-resources, which automates resource management. Mastering these fundamentals prevents silent failures and builds robust, maintainable systems.
Using try-with-resources
Introduced in Java 7, try-with-resources automatically closes resources that implement AutoCloseable, such as InputStream, Connection, or FileChannel. The syntax declares one or more resources in parentheses after try, and the Java runtime invokes their close() method at the end of the block, even if an exception occurs. This eliminates the need for explicit finally blocks for resource cleanup and prevents resource leaks from missed close calls. Critically, try-with-resources handles suppression intelligently: if an exception is thrown in the try body, any exceptions during close() are added as suppressed exceptions to the primary exception, preserving the root cause. This is far superior to traditional try-catch-finally, where close() exceptions often get lost or override the original error. The pattern works with multiple resources, closed in reverse order of declaration. It also supports catch and finally blocks for additional handling, though finally becomes redundant for cleanup. Using try-with-resources is now the industry standard for any resource that must be closed reliably.
close() again on exit, which can double-close and raise suppressed exceptions. Never call close() yourself.Replacing try–catch-finally With try-with-resources
Migrating from traditional try-catch-finally to try-with-resources is straightforward and dramatically improves code safety. The old pattern required declaring a resource outside try, initializing it inside try, and adding a finally block with a null-checked close call that itself needed exception handling. This pattern is verbose and error-prone: a common bug is forgetting the null check, which triggers NullPointerException during cleanup. Worse, if both try and finally throw exceptions, the finally exception silently swallows the original error, making debugging impossible. Try-with-resources eliminates these issues by handling closure automatically and preserving suppressed exceptions. The migration involves moving resource initialization into the try parentheses and removing the finally block entirely. Multiple resources are separated by semicolons and closed in reverse order. Catch blocks remain useful for handling business-specific exceptions, but cleanup logic disappears. The conversion not only reduces lines of code by 30–50% but also eliminates entire classes of resource-leak bugs. Every codebase should prioritize replacing legacy resource management with try-with-resources.
Conclusion
Java's exception handling is a powerful tool when used correctly, but its nuances — from execution flow to resource cleanup — can lead to subtle bugs. The try-catch-finally construct remains foundational, but modern Java strongly favors try-with-resources for any resource implementing AutoCloseable. This pattern eliminates the most common resource management errors: forgotten close calls, null-pointer crashes in finally, and exception suppression that hides root causes. Additionally, understanding exception chaining helps preserve failure context across architectural layers, while structured concurrency in Java 21 introduces new considerations for exception propagation in virtual threads. The key to production-ready exception handling is consistency: declare exceptions with throws, catch at the appropriate abstraction level, and never use exceptions for control flow. Test your exception paths, especially in finally blocks. By following these practices, you ensure that when failures occur, your system responds predictably, logs the complete failure story, and continues operating or degrades gracefully. Mastering exceptions is not just about catching errors — it's about building software that survives them.
The Silent Connection Leak That Took Down a Payment Service
connection.close(), all connections were properly released. Code review showed the finally blocks had null checks. On paper, it looked correct.conn.close(); }. This pattern has a specific failure mode that is easy to miss. When conn.createStatement() threw a SQLException inside the try block, the finally block did execute and called conn.close() — but close() on a connection whose underlying socket had already been torn down by the database server threw its own SQLException. That new exception from inside finally became the active exception, permanently discarding the original SQLException from createStatement(). More critically, the connection pool's bookkeeping never received the close signal it expected — it saw an abnormal teardown rather than a clean return, and treated the connection slot as still in use. Over thousands of requests, the unreturned slots accumulated until the pool hit its configured maximum and began refusing new acquisitions entirely. The fix was a single structural change: move connection acquisition inside the try block and use try-with-resources.close() is called in all exit paths, attaches any close() exception as suppressed rather than replacing the original, and handles the null check internally. For resources that do not implement AutoCloseable, initialise the variable to null before the try block and guard the close call in finally with an explicit null check. Add connection pool monitoring on active connection count as a trend metric, not just an absolute threshold alert — the trend line reveals slow leaks hours before exhaustion.- Never acquire a resource before the try block. If the acquisition itself throws, the resource variable may be partially initialised and the finally block's null check gives false confidence.
- Use try-with-resources for every AutoCloseable resource. It is not syntactic sugar — it fixes a real correctness problem where a throwing
close()discards the original exception. - An exception thrown inside finally replaces and permanently discards whatever exception was already in flight. This is one of the most silent and destructive behaviours in the Java exception model.
- Add connection pool monitoring on active connection count trends, not just absolute exhaustion thresholds. A slow leak is invisible to threshold alerts until it is too late.
close() exception as suppressed. Inspect getSuppressed() on the caught exception to recover the cleanup failure details.grep -rn 'throw new.*Exception(e.getMessage())' src/ — this pattern always loses the original cause. Every match is a bug.Enable -XX:+TraceExceptions JVM flag to log every exception thrown at the JVM level. Use with caution in production — verbosity is extreme. Better for staging reproduction.| File | Command / Code | Purpose |
|---|---|---|
| io | public class ExceptionFlowDemo { | How Execution Actually Flows Through try-catch-finally |
| io | public class ConfigFileReader { | Catching Multiple Exceptions |
| io | /** | Exception Chaining |
| io | public class ResourceCleanup { | finally for Resource Cleanup |
| io | public class FinallyReturnGotcha { | The finally Gotcha |
| io | public class ThrowsDeclarationDemo { | The throws Keyword |
| io | /** | Exception Handling in Java 21 |
| io | public class BestPracticesDemo { | Best Practices for Exception Handling in Production |
| SyntaxBasics.java | public class SyntaxBasics { | The 'Basics' That Burn Juniors |
| ExceptionFloating.java | public class ExceptionFloating { | How an Exception Floats |
| ExceptionOverview.java | public class ExceptionOverview { | Overview |
| TryWithResources.java | public class TryWithResources { | Using try-with-resources |
| ReplaceCatchFinally.java | public class ReplaceCatchFinally { | Replacing try–catch-finally With try-with-resources |
| ExceptionConclusion.java | public class ExceptionConclusion { | Conclusion |
Key takeaways
Runtime.halt(), os-level SIGKILL, and any try block that never exits due to an infinite loop or deadlock. That guarantee is the entire point of finally, and its limits matter as much as the guarantee itself.close() throw, try-with-resources preserves the original as primary and attaches the close exception as suppressed. Manual finally performs a hard replacement, discarding the original entirely.Future.get() pattern.Interview Questions on This Topic
If a try block has a return statement and the finally block also has a return statement, which value does the caller receive — and why?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.
That's Exception Handling. Mark it forged?
14 min read · try the examples if you haven't