Catching errors with the oldest trick in the book

Sometimes, it’s good to remember the old tricks while we’re showered in new fancy stuff. This video is about catching errors and how to do that without TryFunction or collectable errors, check it out:

https://youtu.be/dJSlw0rm9IE

In this video, Erik demonstrates one of the oldest error-handling techniques in AL (and its predecessor C/AL) — a trick that dates back roughly 30 years to the early days of Navision. While modern AL offers try functions and collectible errors, this foundational pattern remains useful and is sometimes overlooked by newer developers.

The Setup: A Problematic Codeunit

Erik starts by creating a simple codeunit that deliberately throws an error. This represents any codeunit in your system that might fail during execution:

codeunit 50100 "Problematic Code Unit"
{
    trigger OnRun()
    begin
        Error('Big problems');
    end;
}

He then creates a page extension on the Customer List with an action to invoke this codeunit. Running it normally results in the expected behavior — the error is thrown, execution stops, and the user sees “Big problems” as an error dialog.

The Oldest Trick in the Book: Wrapping Codeunit.Run in an IF Statement

Here’s the core technique: instead of calling Codeunit.Run as a standalone statement, you wrap it in an IF statement:

if cu.Run() then
    Message('Success')
else
    Message('Something happened');

By encapsulating Codeunit.Run inside an IF statement, the error is not thrown to the user and execution is not stopped. Instead, when an error occurs anywhere within the scope of the codeunit’s execution, control returns to the IF statement with a return value of false. The code gracefully continues into the ELSE branch.

Stepping Through in the Debugger

Erik demonstrates this with the debugger by setting a breakpoint on the IF statement and stepping into the codeunit call (F11). You can see:

  1. Execution enters the codeunit’s OnRun trigger
  2. The Error('Big problems') line is hit
  3. Instead of stopping, execution returns to the IF statement
  4. Codeunit.Run returns false
  5. The ELSE branch executes — “Something happened” is displayed

Retrieving the Error Details

The natural follow-up question is: what error actually occurred? AL provides several built-in functions for this:

  • GetLastErrorCode() — returns an error code (often “Dialog”, which may not be very helpful)
  • GetLastErrorText() — returns the full error message text
  • GetLastErrorCallStack() — returns the call stack at the point of the error
if cu.Run() then
    Message('Success')
else
    Message('Something happened: %1', GetLastErrorText());

Running this now shows: “Something happened: Big problems” — the exact text from the Error call inside the codeunit.

The Transaction Caveat

There is an important limitation to be aware of: this technique does not work when there is an open write transaction.

Erik demonstrates this by adding a Rec.Modify() call before the IF Codeunit.Run statement. This opens a write transaction, and when you then try to use the IF Codeunit.Run pattern, you get an error message explaining that Codeunit.Run is only allowed in a write transaction if the return value is not used.

The reason is that wrapping Codeunit.Run in an IF statement initializes a new transaction, and Business Central does not support nested transactions — you can only have one active transaction at any given time.

The solution: commit before calling the codeunit:

if Rec.FindSet() then
    repeat
        // Do something with Rec
        Commit();

        if not cu.Run() then begin
            Rec.Name2 := CopyStr(GetLastErrorText(), 1, MaxStrLen(Rec."Name 2"));
            Rec.Modify();
        end;
    until Rec.Next() = 0;

A Practical Pattern: Batch Processing with Error Logging

Erik walks through a realistic use case — looping through records, processing each one, and logging errors back to the record when processing fails:

  1. Loop through records (e.g., customers, journal lines, documents)
  2. Perform your preparatory work on each record
  3. Call Commit() to clear the write transaction
  4. Call the processing codeunit wrapped in IF NOT Codeunit.Run() THEN
  5. If it fails, write the error text back to the record (or a log table)
  6. If it succeeds, mark it as posted or processed

One important detail: since GetLastErrorText() can return a very long string (potentially a full screen of text), always use CopyStr to truncate it to the field’s maximum length:

Rec."Name 2" := CopyStr(GetLastErrorText(), 1, MaxStrLen(Rec."Name 2"));

This prevents a runtime error from the error text itself being too long for the target field.

Bonus: The Source Code HttpClient Example

The source code in the extension also includes a nice example of using the same IF pattern with HttpClient.Get, which similarly returns a boolean indicating success or failure:

pageextension 50106 CustomerListExt extends "Customer List"
{
    trigger OnOpenPage()
    var
        Client: HttpClient;
        Response: HttpResponseMessage;
    begin
        if Client.Get('krjfvndkfjghszjfkgbdzkvjzsdfhbv', Response) then begin
            if Response.IsSuccessStatusCode() then
                message('All good! %1', Response.HttpStatusCode());
        end else
            message('%1', GetLastErrorText());
    end;
}

This demonstrates that the pattern of checking a boolean return value and using GetLastErrorText() in the failure branch applies broadly across AL — not just to Codeunit.Run.

Summary

The oldest trick in the book for catching errors in AL is simple: wrap Codeunit.Run in an IF statement. When you do this, any error that occurs within the codeunit’s execution scope is caught, and instead of halting execution, the call returns false. You can then use GetLastErrorText(), GetLastErrorCode(), and GetLastErrorCallStack() to inspect what went wrong.

Key things to remember:

  • This technique has been available since the early 1990s in Navision/NAV/Business Central
  • You cannot have an open write transaction when using this pattern — call Commit() first
  • Business Central does not support nested transactions
  • Always use CopyStr when storing error text in a field to avoid overflow errors
  • While modern alternatives like try functions and collectible errors exist, this pattern remains a fundamental tool in every AL developer’s toolkit