How does try/catch/finally work in EK9?
← Error Handling and Exceptions · Ref: Q135
The finally block in EK9 guarantees cleanup code runs once the try statement is entered, whether the try block succeeds or an exception is thrown and caught.
FULL PATTERN
The complete try/catch/finally structure:
try riskyOperation() catch -> ex as Exception handleError(ex) finally cleanup()
The finally block always runs after try and catch complete.
FINALLY ALWAYS RUNS ONCE THE TRY IS ENTERED
Finally executes in both of these scenarios:
1. Try completes normally (no exception)
2. Exception thrown and caught
This guarantee makes finally ideal for cleanup operations.
AN UNSET GUARD SKIPS EVERYTHING
A try with a guard (try value <- getValue()) is only entered when the guard is set. When the guard is unset the whole statement is skipped: the try body, the catch AND the finally. Finally guarantees cleanup for a statement that was entered, not for one that never started.
TRY/FINALLY WITHOUT CATCH
You can use try/finally without a catch block:
try doWork() finally cleanup()
If an exception occurs, it propagates after finally runs.
CLEANUP PATTERNS
Common uses for finally:
- Logging completion status
- Resetting state
- Releasing resources (though try-with-resources is preferred)
NESTED TRY BLOCKS
Try blocks can be nested. Each has its own catch and finally:
try try innerOperation() catch -> ex as Exception handleInner(ex) finally innerCleanup() finally outerCleanup()
See Q134 for basic try/catch. See Q137 for try-with-resources (automatic cleanup). See Q78 for guard variables in try blocks.
Example
defines module qa.errorhandling.trycatchfinally defines function riskyOperation() -> shouldFail as Boolean <- rtn <- String() if shouldFail ex <- Exception("Operation failed") throw ex rtn: "success" defines program TryCatchFinallyDemo() stdout <- Stdout() // === FULL PATTERN: try/catch/finally === stdout.println("=== Normal execution ===") try result <- riskyOperation(false) stdout.println("Try: " + result) catch -> ex as Exception stdout.println("Catch: " + $ex) finally stdout.println("Finally: always runs") // === EXCEPTION PATH === stdout.println("=== Exception path ===") try result <- riskyOperation(true) stdout.println("Try: " + result) catch -> ex as Exception stdout.println("Catch: " + ex.reason()) finally stdout.println("Finally: still runs after catch") // === TRY/FINALLY WITHOUT CATCH === stdout.println("=== Try/finally without catch ===") try stdout.println("Try: doing work") finally stdout.println("Finally: cleanup without catch") // === NESTED TRY BLOCKS === stdout.println("=== Nested try blocks ===") try try result <- riskyOperation(true) stdout.println("Inner try: " + result) catch -> ex as Exception stdout.println("Inner catch: " + ex.reason()) finally stdout.println("Inner finally") finally stdout.println("Outer finally")
Common mistakes
E07850 — Catch blocks accept only a single exception parameter. EK9 does not support multi-catch syntax like Java. E07850 — only a single Exception is supported. Use the Exception base type and dispatch inside the catch body. See ek9 -h E07850 for details.
Incorrect:
catch -> ex1 as Exception ex2 as Exception stdout.println("Catch: failed")
Correct:
catch -> ex as Exception stdout.println("Catch: " + $ex)
E04030 — The catch parameter must be an Exception type or subtype. Using Integer triggers E04030 — type must be of Exception type. See ek9 -h E04030 for details.
Incorrect:
-> ex as Integer stdout.println("Catch: " + $ex)
Correct:
-> ex as Exception stdout.println("Catch: " + $ex)
E50060 — EK9 Exception has no getMessage() method. Use reason() to get the exception message. See ek9 -h E50060 for details.
Incorrect:
stdout.println("Catch: " + ex.getMessage())
Correct:
stdout.println("Catch: " + ex.reason())
E04030 — Only Exception types can be thrown in EK9. Throwing a String triggers E04030 — type must be of Exception type. Wrap the message in an Exception constructor. See ek9 -h E04030 for details.
Incorrect:
ex <- "Operation failed" throw ex
Correct:
ex <- Exception("Operation failed") throw ex
E50060 — EK9 Exception has no getMessage() method. Use reason() for the exception message. See ek9 -h E50060 for details.
Incorrect:
stdout.println("Inner catch: " + ex.getMessage())
Correct:
stdout.println("Inner catch: " + ex.reason())
E50060 — EK9 has no toString() method. Use string concatenation or interpolation for string conversion. See ek9 -h E50060 for details.
Incorrect:
result <- riskyOperation(false) stdout.println(result.toString())
Correct:
result <- riskyOperation(false) stdout.println("Try: " + result)
Other ways to ask this
- What does finally do in EK9?
- How do I guarantee cleanup in EK9?
- Does EK9 have try-finally?
Coming from another language?
Java: try { } catch { } finally { } with identical guarantee. Python: try: ... except: ... finally: ... same semantics. Rust: no try/finally, uses RAII (Drop trait) for cleanup. Go: defer statement provides cleanup guarantee. Kotlin: try/catch/finally like Java. Swift: defer keyword for cleanup (runs when scope exits, same purpose as finally), no try/finally syntax. EK9: try/catch/finally with the same guarantee once the try is entered; with a guard variable, an unset guard skips the whole statement, finally included.
Keywords: always, finally, resource, nested, swift, defer, pattern, handle, cleanup, catch, error, exception, guarantee, try