How do I throw exceptions in EK9?

← Error Handling and Exceptions · Ref: Q136

EK9 uses the throw keyword to raise exceptions. You can throw the built-in Exception or create custom exception types by extending Exception.

CREATING EXCEPTIONS

Create an Exception with a reason string:

  ex <- Exception("Something went wrong")

Or with a reason and exit code:

  ex <- Exception("Fatal error", 1)

The exit code is used if the exception propagates to the program entry point.

THROW KEYWORD

Throw an exception variable:

  ex <- Exception("Error occurred")
  throw ex

The throw statement must reference a variable, not an inline expression.

CUSTOM EXCEPTION TYPES

Define custom exceptions by extending Exception:

  ValidationError extends Exception
    field <- String()
    ValidationError()
      ->
        reason as String
        field as String
      super(reason)
      this.field :=: field
    field() as pure
      <- rtn as String: field
    default operator ?

Custom exceptions can carry additional context beyond the reason string.

EXCEPTION PROPERTIES

All exceptions have:

  ex.reason()     the error message
  ex.exitCode()   optional exit code
  $ex             string representation
  ex?             isSet check

WHEN TO THROW

Throw exceptions for truly unexpected, exceptional conditions:
- Invalid arguments that indicate programmer error
- System failures (I/O errors, resource unavailability)
- Broken invariants that should never occur
For expected failures (validation, missing data), prefer the Result type instead.

See Q134 for try/catch. See Q138 for catching specific exception types. See Q139 for choosing between try/catch and Result.

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

Example

defines module qa.errorhandling.throwexception

  defines class

    <?-
      Custom exception for validation errors with field name context.
    -?>
    ValidationError extends Exception
      field <- String()

      ValidationError()
        ->
          reason as String
          field as String

        super(reason)
        this.field :=: field

      field() as pure
        <- rtn as String: field

      default operator ?

  defines function

    validateAge()
      -> age as Integer
      <- rtn <- String()

      if age < 0
        ex <- ValidationError("Age cannot be negative", "age")
        throw ex
      maxAmount <- 150
      if age > maxAmount
        ex <- ValidationError("Age unreasonably large", "age")
        throw ex
      rtn: "Valid age: " + $age

  defines program

    ThrowExceptionDemo()
      stdout <- Stdout()

      // === THROWING BUILT-IN EXCEPTION ===

      try
        ex <- Exception("Something went wrong")
        throw ex
      catch
        -> ex as Exception
        stdout.println("Caught: " + ex.reason())

      // === THROWING CUSTOM EXCEPTION ===

      try
        result <- validateAge(-5)
        stdout.println(result)
      catch
        -> ex as ValidationError
        stdout.println(`Validation failed on field '${ex.field()}': ${ex.reason()}`)

      // === SUCCESSFUL PATH ===

      try
        result <- validateAge(25)
        stdout.println(result)
      catch
        -> ex as ValidationError
        stdout.println("Should not reach here")

      // === EXCEPTION WITH EXIT CODE ===

      try
        ex <- Exception("Fatal startup error", 1)
        throw ex
      catch
        -> ex as Exception
        stdout.println("Exit code: " + $ex.exitCode())

Common mistakes

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 Exception uses exitCode(), not getExitCode(). EK9 does not use Java-style getter naming. See ek9 -h E50060 for details.

Incorrect:

stdout.println("Exit code: " + ex.getExitCode())

Correct:

stdout.println("Exit code: " + $ex.exitCode())

E50060 — The method is field(), not getField(). EK9 does not use Java-style getter naming conventions. See ek9 -h E50060 for details.

Incorrect:

stdout.println(`Validation failed on field '${ex.getField()}': ${ex.reason()}`)

Correct:

stdout.println(`Validation failed on field '${ex.field()}': ${ex.reason()}`)

E04030 — Only Exception types (or subtypes) can be thrown. 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 <- "Something went wrong"
        throw ex

Correct:

ex <- Exception("Something went wrong")
        throw ex

E04030 — Only Exception types can be thrown in EK9. Throwing a String triggers E04030. Create an Exception with the message. See ek9 -h E04030 for details.

Incorrect:

ex <- "Fatal startup error"
        throw ex

Correct:

ex <- Exception("Fatal startup error", 1)
        throw ex
Other ways to ask this
  • How do I raise an error in EK9?
  • How do I create custom exceptions?
  • How do I extend Exception in EK9?

Coming from another language?

Java: throw new SomeException("msg") with extends Exception or RuntimeException. Python: raise ValueError("msg") with class hierarchy. Rust: no throw, uses Result::Err() or panic!(). Go: no throw, uses error return values. Kotlin: throw SomeException("msg") like Java. Swift: throw SomeError() with Error protocol conformance, throws keyword required on function signatures. EK9: throw ex where ex is a variable (not inline new), custom exceptions use 'extends Exception' with additional fields, default operator ? required.

Keywords: catch, custom, extends, error, exception, throw, raise, reason, exitCode, handle, swift, create