13.3 Error Handling: ErrorInfo, TryFunctions & Collectible Errors

Key Takeaways

  • Traditional error handling via Error(), FieldError(), and TestField() halts code execution immediately, presents a modal dialog, and rolls back all database modifications in the current transaction.
  • The modern ErrorInfo object allows developers to construct rich, structured errors containing custom titles, actionable FixIt buttons, navigation links, and telemetry metadata.
  • Collectible errors configured with [ErrorBehavior(ErrorBehavior::Collect)] allow procedures to gather multiple validation errors across a batch process and present them all together in a single consolidated dialog.
  • TryFunctions ([TryFunction]) catch runtime exceptions without displaying error dialogs to the user, returning false upon failure and capturing error details in GetLastErrorText().
  • Crucial TryFunction Restriction: Any procedure marked as a [TryFunction] (or any child procedure called within it) is strictly forbidden from performing database write operations (Insert, Modify, Delete, ModifyAll, DeleteAll) or calling Commit().
Last updated: August 2026

13.3 Error Handling: ErrorInfo, TryFunctions & Collectible Errors

Robust error handling is essential for mission-critical enterprise ERP systems. In Microsoft Dynamics 365 Business Central, unhandled errors immediately halt execution and roll back active database transactions to preserve data integrity. Modern AL provides advanced error-handling architectures, including structured ErrorInfo objects, actionable FixIt buttons, collectible error batching, and non-fatal [TryFunction] exception catching. For the MB-820 exam, developers must master traditional error statements, the modern ErrorInfo framework, and the strict transactional restrictions governing TryFunctions.


1. Traditional Error Handling & Assertions

AL includes three classic error-raising methods designed to validate business rules and abort invalid operations.

local procedure ValidateCustomerRecord(var Customer: Record Customer)
begin
    // 1. TestField Assertion: Verifies field is not blank or matches expected value
    Customer.TestField("Gen. Bus. Posting Group"); // Throws error if blank
    Customer.TestField("Credit Limit (LCY)", 10000); // Throws error if not equal to 10000

    // 2. FieldError: Localized contextual field error
    if Customer."Credit Limit (LCY)" < 0 then
        Customer.FieldError("Credit Limit (LCY)", 'cannot be negative');

    // 3. Standard Error: Custom formatted message
    if Customer.Blocked = Customer.Blocked::All then
        Error('Customer %1 is blocked for all transactions.', Customer."No.");
end;

Traditional Error Methods Comparison

  • TestField(Field [, ExpectedValue]): Asserts that a field contains a non-blank (non-zero/non-empty) value. If an ExpectedValue is supplied, it verifies that the field equals that exact value. If the test fails, AL throws a standardized, localized runtime error (e.g., 'Gen. Bus. Posting Group must have a value in Customer: No.=10000').
  • FieldError(Field [, CustomText]): Throws an error specifically tied to a field. Business Central automatically formats the error message to include the table name, primary key, field caption, and the invalid value.
  • Error(Message [, Param1...]): Throws an unhandled exception with a custom message string, immediately halting execution and displaying a modal error dialog.

Transactional Impact: All traditional error methods (Error, FieldError, TestField) trigger an immediate transaction rollback. Any pending database inserts, modifications, or deletions executed during the current transaction are discarded.

2. Modern Error Framework: ErrorInfo & Actionable Errors

To modernize the user experience, AL introduced the ErrorInfo object. Instead of plain text modal dialogs, ErrorInfo enables rich, structured error cards equipped with diagnostic telemetry, navigation links, and direct remedial action buttons ("Fix It").

local procedure CheckPostingSetupWithFixAction(CustPostingGroupCode: Code[20])
var
    CustPostingGroup: Record "Customer Posting Group";
    MyErrorInfo: ErrorInfo;
begin
    if not CustPostingGroup.Get(CustPostingGroupCode) then begin
        MyErrorInfo.Title('Missing Customer Posting Group');
        MyErrorInfo.Message(StrSubstNo('The Customer Posting Group "%1" does not exist.', CustPostingGroupCode));
        MyErrorInfo.DetailedMessage('Configure the posting group before attempting to post sales documents.');
        MyErrorInfo.ErrorType := ErrorType::Client;
        MyErrorInfo.Verbosity := Verbosity::Error;
        MyErrorInfo.DataClassification := DataClassification::CustomerContent;
        
        // Add an Actionable FixIt button to the error dialog
        MyErrorInfo.AddAction('Create Posting Group', Codeunit::"Posting Setup Fixes", 'CreateMissingGroup');
        
        // Associate error with specific table and field for UI highlighting
        MyErrorInfo.RecordId := CustPostingGroup.RecordId;
        MyErrorInfo.FieldNo := CustPostingGroup.FieldNo(Code);

        Error(MyErrorInfo);
    end;
end;

ErrorInfo Key Properties & Methods

  • Title: Short summary header displayed prominently at the top of the modern error card.
  • Message: Primary user-facing error explanation.
  • DetailedMessage: Extended technical details for administrators and developers.
  • ErrorType: Categorizes the error (ErrorType::Client for user validation, ErrorType::Internal for platform exceptions).
  • RecordId / FieldNo / TableId: Binds the error to a specific record and field, allowing the web client to highlight the invalid field on the user interface.
  • AddAction(Title, CodeunitId, MethodName): Adds an actionable button to the error dialog that runs a specific procedure to rectify the issue automatically.
  • AddNavigationAction(Title): Opens the relevant setup page or document card directly from the error window.

3. Collectible Errors: Batch Validation

In high-volume batch processing (such as posting 500 journal lines or importing 1,000 orders), throwing an immediate Error() on the first invalid row forces the user into a frustrating cycle of fixing one error at a time. Collectible Errors allow AL code to gather all validation failures and present them in a single comprehensive list.

[ErrorBehavior(ErrorBehavior::Collect)]
procedure ValidateBatchSalesOrders(var SalesHeader: Record "Sales Header")
var
    OrderErrorInfo: ErrorInfo;
begin
    if SalesHeader.FindSet() then
        repeat
            if SalesHeader."Posting Date" = 0D then begin
                OrderErrorInfo := ErrorInfo.Create(
                    StrSubstNo('Order %1 is missing a Posting Date.', SalesHeader."No."),
                    true // Collectible = true
                );
                OrderErrorInfo.RecordId := SalesHeader.RecordId;
                OrderErrorInfo.FieldNo := SalesHeader.FieldNo("Posting Date");
                Error(OrderErrorInfo); // Does NOT halt execution; registers error in collection
            end;

            if SalesHeader."Customer Posting Group" = '' then begin
                OrderErrorInfo := ErrorInfo.Create(
                    StrSubstNo('Order %1 is missing Customer Posting Group.', SalesHeader."No."),
                    true
                );
                OrderErrorInfo.RecordId := SalesHeader.RecordId;
                Error(OrderErrorInfo);
            end;
        until SalesHeader.Next() = 0;
end;

Mechanics of Collectible Errors

  1. The executing procedure must be decorated with the attribute [ErrorBehavior(ErrorBehavior::Collect)].
  2. The ErrorInfo instance is initialized as collectible (e.g., ErrorInfo.Create('Message', true) or setting ErrorInfo.Collectible := true).
  3. When Error(MyErrorInfo) is invoked, the AL runtime does not abort execution. Instead, it adds the ErrorInfo to an internal collection and continues executing the remainder of the routine.
  4. Developers can query collected errors programmatically using system.GetCollectedErrors() or allow the runtime to display the aggregated error review page upon procedure termination.
Loading diagram...
AL Error Handling & TryFunction Transaction Lifecycle

4. TryFunctions: Mechanics & Strict Restrictions

TryFunctions allow developers to catch and handle runtime exceptions in AL without aborting code execution or displaying error dialogs to the user.

local procedure SafeProcessExternalOrder(OrderPayload: Text): Boolean
var
    ErrorMessage: Text;
begin
    // Invoke TryFunction
    if not TrySendOrderToApi(OrderPayload) then begin
        ErrorMessage := GetLastErrorText();
        // Log error to telemetry or integration log table
        LogIntegrationError('OrderSync', ErrorMessage);
        ClearLastError();
        exit(false);
    end;
    exit(true);
end;

[TryFunction]
local procedure TrySendOrderToApi(Payload: Text)
var
    HttpClient: HttpClient;
    HttpResponse: HttpResponseMessage;
    HttpContent: HttpContent;
begin
    HttpContent.WriteFrom(Payload);
    // If network fails or timeout occurs, TryFunction intercepts error
    if not HttpClient.Post('https://api.logistics.com/orders', HttpContent, HttpResponse) then
        Error('Failed to establish connection to logistics endpoint.');

    if not HttpResponse.IsSuccessStatusCode() then
        Error('Logistics API returned HTTP Status %1', HttpResponse.HttpStatusCode());
end;

Key TryFunction System Functions

  • GetLastErrorText(): Returns the error message string generated by the failed TryFunction.
  • GetLastErrorCallStack(): Returns the AL execution call stack leading to the error.
  • ClearLastError(): Clears the last recorded error from session memory.

The Golden Rule of TryFunctions (MB-820 Exam Essential)

CRITICAL EXAM WARNING: Procedures marked with [TryFunction] (and any procedure called within their scope) CANNOT perform database write operations (Insert, Modify, Delete, ModifyAll, DeleteAll) or invoke Commit().

If any database write operation is attempted inside a TryFunction, the Business Central runtime immediately throws a fatal exception: "The following C/AL functions can be used only to read the database, not to write to it: Insert, Modify, Delete... in a TryFunction."

Correct Use Case: TryFunctions must be used strictly for non-database operations: calling external REST/SOAP APIs, parsing JSON/XML payloads, evaluating complex math, or interacting with .NET/Azure services.

Test Your Knowledge

A developer writes a procedure marked with the [TryFunction] attribute. Inside the procedure, the code executes: Customer.Get('10000'); Customer."Credit Limit (LCY)" := 50000; Customer.Modify(true);. What happens when this TryFunction is invoked at runtime?

A
B
C
D
Test Your Knowledge

A developer is building a validation routine for a sales order import. When a customer has an invalid posting group, the developer wants the error dialog to provide a direct, clickable button that opens the Customer Card so the user can fix the setup. Which AL construct should the developer implement?

A
B
C
D
Test Your Knowledge

A developer needs to validate 2,000 imported journal lines. Rather than aborting the import on the first invalid line, the developer wants to collect all validation errors across all 2,000 lines and display them together in a consolidated list. Which AL design pattern must be implemented?

A
B
C
D
Test Your Knowledge

An AL integration procedure invokes an external REST service using a TryFunction named TryPostToLogisticsApi(). If the HTTP call fails due to a network timeout, which AL system method retrieves the failure message string generated during the call?

A
B
C
D