How do I use try/catch to handle exceptions in EK9?

← Error Handling and Exceptions · Ref: Q134

EK9 uses try/catch blocks to handle exceptions, similar to Java, Python, and other languages. The catch block binds the exception to a variable using the arrow syntax.

BASIC TRY/CATCH

Wrap code that might throw in a try block, and handle errors in catch:

  try
    riskyOperation()
  catch
    -> ex as Exception
    handleError(ex)

The catch block only executes if an exception is thrown inside the try block.

EXCEPTION BINDING

The catch block declares an exception variable with arrow syntax:

  catch
    -> ex as Exception

This binds the caught exception to the variable 'ex' for use within the catch body.

ACCESSING EXCEPTION INFO

Exception provides reason() and the $ operator for string conversion:

  catch
    -> ex as Exception
    stdout.println(ex.reason())
    stdout.println($ex)

The reason() method returns the message. The $ operator converts the full exception to a string.

HANDLE SYNONYM

EK9 supports 'handle' as a synonym for 'catch'. Both are identical:

  try
    riskyOperation()
  handle
    -> ex as Exception
    handleError(ex)

Use whichever reads better in context.

TRY AS EXPRESSION

Try can be used as an expression that returns a value:

  result <- try
    <- rtn as String: "default"
    rtn: computeValue()
  catch
    -> ex as Exception
    rtn: "error: " + $ex

The return variable must be declared in the try body.

See Q78 for guard variables in try blocks. See Q48 for the Result type alternative. See Q135 for try/catch/finally. See Q136 for throwing exceptions. See Q246 for debugging strategies. See Q283 for retry logic without break.

See Q304 for require preconditions. See Q305 for require vs assert vs throw.

See Q333 for transaction try-with-resources. See Q338 for partial commit prevention.

Example

defines module qa.errorhandling.trycatch

  defines function

    riskyDivide()
      ->
        a as Integer
        b as Integer
      <- rtn <- Integer()

      if b == 0
        ex <- Exception("Division by zero")
        throw ex
      rtn: a / b

  defines program

    TryCatchBasics()
      stdout <- Stdout()

      // === BASIC TRY/CATCH ===

      try
        result <- riskyDivide(10, 2)
        stdout.println("Result: " + $result)
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)

      // === CATCHING AN EXCEPTION ===

      try
        result <- riskyDivide(10, 0)
        stdout.println("Result: " + $result)
      catch
        -> ex as Exception
        stdout.println("Caught: " + ex.reason())

      // === HANDLE SYNONYM ===

      try
        result <- riskyDivide(5, 0)
        stdout.println("Result: " + $result)
      handle
        -> ex as Exception
        stdout.println("Handled: " + $ex)

      // === TRY AS EXPRESSION ===

      message <- try
        <- rtn as String: "no result"
        value <- riskyDivide(20, 4)
        rtn: "Computed: " + $value
      catch
        -> ex as Exception
        rtn: "Failed: " + ex.reason()

      stdout.println(message)

Common mistakes

E50010 — EK9 catch blocks accept only a single exception parameter. Unlike Java's multi-catch, you cannot list multiple exception types. E50010 is triggered — only a single Exception is supported. Catch the common base type (Exception) and check specific types within the block. See ek9 -h E50010 for details.

Incorrect:

catch
        ->
          ex1 as ValidationError
          ex2 as Exception

Correct:

catch
        -> ex as Exception

E04030 — The catch variable must be an Exception type or a subclass of Exception. Using a non-exception type like String triggers E04030 — type must be of Exception type. Define custom exceptions with 'extends Exception'. See ek9 -h E04030 for details.

Incorrect:

catch
        -> ex as String

Correct:

catch
        -> ex as Exception

E04030 — Only Exception types (or subtypes) 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 <- "Division by zero"
        throw ex

Correct:

ex <- Exception("Division by zero")
        throw ex

E50060 — EK9 Exception has no getMessage() method. Use reason() to get the exception message. See ek9 -h E50060 for details.

Incorrect:

stdout.println("Caught: " + ex.getMessage())

Correct:

stdout.println("Caught: " + ex.reason())

E50060 — EK9 has no toString() method. Use the $ operator or string interpolation for string conversion. See ek9 -h E50060 for details.

Incorrect:

stdout.println(ex.toString())

Correct:

stdout.println("Error: " + $ex)

E50060 — EK9 Exception has no getMessage() method. Use reason() to get the exception message. See ek9 -h E50060 for details.

Incorrect:

rtn: "Failed: " + ex.getMessage()

Correct:

rtn: "Failed: " + ex.reason()

E50060 — EK9 has no toString() method. Use the $ operator for string conversion. See ek9 -h E50060 for details.

Incorrect:

stdout.println(ex.toString())

Correct:

stdout.println("Handled: " + $ex)
Other ways to ask this
  • What is exception handling in EK9?
  • How does try-catch work in EK9?
  • What is the EK9 equivalent of Java try-catch?
  • How do I catch errors in EK9?

Coming from another language?

Java: try { ... } catch (Exception e) { ... } with checked/unchecked distinction. Python: try: ... except Exception as e: ... with duck-typed exceptions. Rust: no try/catch, uses Result<T,E> and ? operator. Go: no try/catch, uses error return values. Kotlin: try/catch like Java but all exceptions unchecked. Swift: do { try expr() } catch { error }, throws keyword marks throwing functions, try? converts to optional, try! force-unwraps (crashes on error). EK9: try/catch with arrow binding (-> ex as Exception), 'handle' synonym for 'catch', all exceptions unchecked, try can be an expression.

Keywords: catch, error, exception, throw, binding, try, syntax, basic, reason, handle, swift, migrate