[Ballerina] Error Handling — Part I

Maryam Ziyad
Ballerina Swan Lake Tech Blog
5 min readSep 28, 2019
Photo by John Schnobrich on Unsplash

NOTE: This blog is based on an older version of Ballerina and is therefore, incompatible with newer/current versions of Ballerina. An updated version of this blog is now available as a Stack Overflow article and a Medium blog.

Errors in Ballerina are quite explicit and pretty much impossible to ignore by design.

Ballerina has a built-in error type, to which any and all error values created belong to.

The Built-in `error` Type

The error type has two components:

  • reason — a string representing the reason for the error
  • detail — a mapping giving additional details about the error

The detail mapping has the following structure:

record {|
string message?;
error cause?;
(anydata|error)...;
|};

The message field is an optional field of type string while cause is an optional field of type error. The detail mapping could additionally hold any value of a type that is a subtype of anydata|error.

Creating Error Values and Accessing the Error Reason/Detail

An error value of type error can be created using the direct error constructor.

error e = error("error reason", message = "error message",
code = 404);
  • The first argument to the direct error constructor is expected to be a positional argument, which will be the error reason.
  • The rest of the arguments, if present, are expected to be named arguments representing the fields of the error detail mapping.

The error reason argument is mandatory here, but the arguments for the detail mapping are optional.

e.g.,

error e = error("error with only a reason");

If no detail arguments are specified, the detail would be an empty mapping.

The reason and the detail mapping can be accessed using the .reason() and .detail() methods available on any error variable.

Running the above file would result in the following output:

$ ballerina run error_reason_detail_access.bal
e1 reason: error reason
e1 detail length: 2
e1 detail message: error message
e1 detail code: 404
e2 reason: error with only a reason
e2 detail length: 0

An error value can be used similar to other values — e.g., can be passed as arguments, can be returned from functions, etc.

However, the error type (and an error value) does have some differences that differentiates it from the other types/values:

  • An error value cannot be assigned to a variable of type any — the any type in Ballerina represents all values in Ballerina, except for error values.
  • An error value cannot be ignored using the wildcard (_).

The following results in compilation errors:

An error value can be returned from a function or can cause abrupt termination via panic.

Returning Errors

A function may return an error if and only if error is part of the return signature of the function.

$ ballerina run error_returning_function.bal
error InvalidName message=invalid length
error InvalidName message=contains spaces

Abrupt Termination via Panic

An error value can also result in abrupt termination via panic, if used in a panic statement.

If not explicitly handled with trap (explained later on) along the call-stack, a panic results in program termination.

$ ballerina run panicking_function.bal 
Updated Account for ID 2500 , Amount: 1500.00
error: InvalidAccountId code=1234
at panicking_function:updateAccount(panicking_function.bal:9)
panicking_function:main(panicking_function.bal:18)

The stack-trace is printed for the panicked error.

Panicking is only expected to be done in exceptional scenarios — i.e., non-recoverable scenarios (e.g., out of memory errors, index out of range errors, etc.). Business logic related errors would usually be returned.

Handling Returned Errors

If a function could potentially return an error, the result of the invocation could be used in a type test to identify if an error occurred, and act accordingly.

Note how the registerAccount() function only adds an account if the username validation does not return an error. The type test/type guard is used to check if the returned value is in fact an error, an use it as an error if so.

- check and checkpanic

check

In certain scenarios, if an expression (or action) evaluates to an error, you would want to return the error as is without further processing. Introducing a type test to check if the value is an error, and just return if so, seems like unnecessary overhead.

The check expression could be used in such a scenario.

Where a function foo() returns int|error, check can be used with the invocation foo().

check foo();

If the type of the result of the foo() invocation is error, the rest of the block is not evaluated, instead the error value which was the result of foo() is returned — thus, obviously, the function in which the check foo() is done should have error as a possible return type.

The type of the check foo() expression itself would be the type of foo() minus error — i.e., int.

int i = check foo();
$ ballerina run error_check.bal
error InvalidName message=invalid length
Added Account ID: 1001
1001

Note how the logic after the check-ed expression is not evaluated if validateUsername() returns an error.

checkpanic

Similarly you may want to terminate the program, if a particular expression expression (or action) evaluates to error.

One way to do this would be to evaluate the expression (or action) and obtain the result, do the type test and then panic if it is an error.

For example, where foo() returns int|error

int|error res = foo();if (res is error) {
panic res;
} else {
// `res` is an `int` here
}

Alternatively, you could use checkpanic which works similar to check, except that instead of returning the error if the expression (or action) evaluates to error, it panics.

$ ballerina run error_checkpanic.bal
error: InvalidName message=invalid length
at xyz:validateUsername(xyz.bal:31)
xyz:registerAccount(xyz.bal:17)
xyz:main(xyz.bal:8)

Note how the program terminates once panicked.

Handling Panics

Given that a particular expression (or action) could panic, you may also want to avoid program termination by handling the associated error yourself. This could be done using trap.

When used with a function foo() that returns int|string , the resultant type of trap foo() would be the union of the type of foo(), which is int|string, and error.

$ ballerina run error_trap.bal 
1
error not one or two
Done

Note how the program does not terminate abruptly, even though the second foo() invocation panics.

This was just a quick introduction to how one can work with the error type in Ballerina.

In the upcoming posts we will look into defining and working with custom errors, advanced operations with errors, etc.

The official BBEs on errors in Ballerina are specified under the “Errors” section at https://ballerina.io/learn/by-example/

Cheers!

--

--