How does try/catch/finally work in EK9?

← Error Handling and Exceptions · Ref: Q135

The finally block in EK9 guarantees cleanup code runs regardless of whether the try block succeeds, an exception is caught, or a guard is unset.

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

Finally executes in all three scenarios:
1. Try completes normally (no exception)
2. Exception thrown and caught
3. Guard variable is unset (try body skipped)
This guarantee makes finally ideal for cleanup operations.

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

E50001 — Catch blocks accept only a single exception parameter. EK9 does not support multi-catch syntax like Java. E50001 — only a single Exception is supported. Use the Exception base type and dispatch inside the catch body. See ek9 -h E50001 for details.

Incorrect:

catch
        ->
          ex1 as Exception
          ex2 as Exception

Correct:

catch
        -> ex as Exception

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:

stdout.println(result.toString())

Correct:

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 same guarantee, also works with guard variables (unset guard skips try body but finally still runs).

Keywords: handle, pattern, defer, swift, nested, resource, finally, always, try, guarantee, exception, error, catch, cleanup