Skip to main content

Try / Catch

try — Error handling construct

The TRY construct is the primary control flow structure for handling errors, warnings, exceptions, and other special conditions that change the normal execution flow. It establishes a protected region of code: if any statement within the TRY block throws an error (either explicitly with <error> or implicitly from a runtime failure), execution immediately jumps to the <catch> block instead of terminating the entire service.

The TRY construct supports up to four sections, each with distinct execution semantics:

  1. The try body — the protected code that might fail
  2. catch — code that runs only when an error occurs
  3. else — code that runs only when no error occurs
  4. finally — code that always runs, regardless of whether an error occurred

This four-part structure gives you precise control over both the success and failure paths of any operation, while ensuring that cleanup code (like closing resources or sending response headers) always executes.

Full reference →

Syntax

XML
<try>
<!-- Protected code that might throw an error -->
<set var="data">some operation</set>

<catch var="errorMessage">
<!-- Runs ONLY if an error was thrown in the try body -->
<output>Error: $errorMessage</output>
</catch>

<else>
<!-- Runs ONLY if NO error was thrown in the try body -->
<output>Success</output>
</else>

<finally>
<!-- ALWAYS runs, regardless of success or failure -->
<output> (done)</output>
</finally>
</try>

Child element: catch — Error-triggered execution

Occurrence: "single"

The <catch> block executes only when an error is thrown within the <try> body. When an error occurs, execution of the <try> body immediately stops at the point of failure — any statements after the error are skipped — and control transfers to the <catch> block.

The <catch> block provides access to the error message through its var attribute. This variable receives a string containing the error message text, which you can use for logging, user-facing error responses, or conditional error handling logic. If the var attribute is omitted, the <catch> block still executes on error, but you lose access to the error message. This is acceptable when you know what the error will be and don't need the specific message, but in most cases you should capture it for logging or diagnostics.

Full reference →

Child element: else — Success-path execution

Occurrence: "single"

The <else> block executes only when the <try> body completes successfully without any errors. This is the inverse of <catch> — exactly one of <catch> or <else> will execute for any given <try> invocation, never both.

The <else> block is useful for actions that should only happen on success, such as committing a transaction, sending a success response, or updating a status flag. Placing these actions in the <else> block rather than at the end of the <try> body ensures they never execute if an error occurred partway through the body.

Child element: finally — Guaranteed execution

Occurrence: "single"

The <finally> block always executes after the <try> body and whichever of <catch> or <else> ran. It executes regardless of whether an error occurred, making it the ideal place for cleanup code that must run in all circumstances — sending HTTP headers, closing connections, outputting final response wrappers, or releasing resources.

Full reference →

Execution flow

Understanding the exact order of execution is critical for writing correct error handling code. Here are the two possible flows:

When no error occurs:

  1. The <try> body executes completely
  2. The <else> block executes (if present)
  3. The <finally> block executes (if present)

When an error occurs:

  1. The <try> body executes until the error is thrown
  2. Remaining statements in the <try> body are skipped
  3. The <catch> block executes (if present), with the error message in var
  4. The <finally> block executes (if present)
note

If <catch> is not present and an error occurs, the error propagates upward to the next enclosing <try> block, or terminates execution if there is none. Always include a <catch> block unless you intentionally want errors to propagate.

tip

You can nest <try> blocks to handle errors at different scopes. An inner <catch> handles its own scope; if re-thrown with <error>, the outer <catch> handles it.

Examples

Basic error handling:

This example demonstrates the simplest form of try/catch — protecting code that might fail and capturing the error message:

XML
<try>
<set var="name">iXML</set>

<if value1="$name" func="=" value2="iXML">
<error>An error has occurred!</error>
</if>

<catch var="error">
<output>$error</output>
</catch>
</try>

<!-- An error has occurred! -->

The <error> statement throws an exception with the message "An error has occurred!". Execution immediately jumps to the <catch> block, which receives the message in the $error variable and outputs it. The <if> closing tag and any subsequent statements in the <try> body are skipped.

Extended error handling with else and finally:

This example shows the full four-part structure. The <else> block overwrites the initial status because no error occurs, and <finally> outputs the final value regardless:

XML
<try>
<set var="name">iXML</set>

<catch>
<set var="output">My name is unknown!</set>
</catch>

<else>
<set var="output">My name is $name!</set>
</else>

<finally>
<output>$output</output>
</finally>
</try>

<!-- My name is iXML! -->

Since no error occurs in the <try> body, the <catch> block is skipped, the <else> block sets output to "My name is iXML!", and the <finally> block outputs the result.

Defensive JSON parsing:

One of the most common error handling patterns in ZeyOS services is protecting against malformed JSON input. The <decode:json> command throws an error when the input is not valid JSON, which you can catch and convert into a structured error response:

XML
<array var="RES" />

<try>
<decode:json var="payload">$BODY</decode:json>
<set var="RES.result">1</set>
<set var="RES.data" var_source="payload" />

<catch var="err">
<set var="RES.error">Invalid JSON input</set>
<set var="RES.details">$err</set>
</catch>
</try>

<header>Content-Type: application/json</header>
<output><encode:json var="RES" /></output>

If the JSON is valid, the response contains {"result": 1, "data": {...}}. If parsing fails, the response contains {"error": "Invalid JSON input", "details": "Syntax error"}. In both cases, the client receives a properly formatted JSON response with appropriate HTTP headers.

Database error wrapping:

When performing multi-step database operations, wrap the entire sequence in <try>/<catch> combined with <db:transaction> to ensure atomicity. If any statement fails, the transaction is automatically rolled back and the error is caught:

XML
<try>
<db:transaction>
<db:insert table="contacts" var_result="contactId">
<db:value field="lastname">$payload.lastname</db:value>
<db:value field="firstname">$payload.firstname</db:value>
</db:insert>

<db:insert table="tasks">
<db:value field="contactid">$contactId</db:value>
<db:value field="name">Welcome task</db:value>
</db:insert>
</db:transaction>

<catch var="err">
<header status="500" />
<set var="RES.error">Database operation failed</set>
<set var="RES.details">$err</set>
</catch>

<else>
<set var="RES.result">1</set>
<set var="RES.contactId">$contactId</set>
</else>

<finally>
<header>Content-Type: application/json</header>
<output><encode:json var="RES" /></output>
</finally>
</try>

The <finally> block ensures the JSON response is always sent, whether the database operations succeeded or failed. The <else> block only populates success data if no error occurred. The <catch> block sets the HTTP status to 500 and populates error details.

External service error handling:

When calling external REST or SOAP services, network failures, timeouts, and unexpected response formats can all throw errors. Wrap external calls in <try>/<catch> and provide fallback behavior:

XML
<try>
<rest:client url="https://api.example.com" timeout="10">
<rest:bind var="getStatus" method="GET">/status</rest:bind>
</rest:client>

<call func="getStatus" var="statusData" />

<catch var="err">
<set var="statusData.available">0</set>
<set var="statusData.error">$err</set>
</catch>
</try>

<!-- statusData is now populated regardless of whether the external call succeeded -->