[Ballerina] Error Handling — Part I
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
— theany
type in Ballerina represents all values in Ballerina, except forerror
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!