← Back to TutorialsAL DEVELOPMENT

Business Central AL Error Handling: Error, TryFunction & Logging

Dynexal • Beginner to Intermediate • 14 min read

Errors are unavoidable in Business Central development, especially when you work with posting routines, APIs, external services, permissions and data validation. Good AL error handling should do more than stop execution: it should give users a clear next step, preserve useful diagnostic information and make production problems easier to investigate. In this practical guide, you will learn how to use Error, ErrorInfo, TryFunction, Codeunit.Run, GetLastErrorText, GetLastErrorCallStack and telemetry in maintainable AL solutions.

What you will learn: AL error basics, ErrorInfo, TryFunction, Codeunit.Run, last-error methods, transaction considerations, API/integration error handling, telemetry, debugging, common mistakes and production best practices.

What is error handling in Business Central AL?

Error handling is the way an AL application detects an invalid or failed operation and decides what should happen next. A validation error may need to stop the current process and tell the user how to fix the data. An integration failure may need to capture the response, log diagnostic details and allow a background process to continue with other work.

Business Central provides several approaches, including the Error method, ErrorInfo, TryFunction, the Boolean return value of Codeunit.Run, last-error methods and telemetry. Microsoft also recommends clear, actionable error messages because good messages reduce user friction and make troubleshooting easier. Microsoft Learn: AL error handling

1. The Error method

The simplest way to stop execution and report a problem is the Error method. When it is raised, execution of the current AL flow stops and Business Central displays the error.

procedure ValidateQuantity(Quantity: Decimal)
begin
    if Quantity <= 0 then
        Error('Quantity must be greater than zero.');
end;

For new production code, prefer labels rather than hard-coded user-facing strings so messages can be translated and maintained.

var
    QuantityErr: Label 'Quantity must be greater than zero.';

procedure ValidateQuantity(Quantity: Decimal)
begin
    if Quantity <= 0 then
        Error(QuantityErr);
end;
Rule: use Error when the current operation cannot safely continue. Do not use errors as normal control flow for expected success/failure decisions when a Boolean result or another explicit design would be clearer.

2. ErrorInfo: better error messages

ErrorInfo provides a structured way to describe an error. It can carry information such as a title, message, detailed message, error type, data classification and actionable navigation or fix actions. This allows an error to be more useful than a plain text string.

procedure ValidateCustomer(CustomerNo: Code[20])
var
    CustomerErr: ErrorInfo;
begin
    if CustomerNo = '' then begin
        CustomerErr.Title('Customer is required');
        CustomerErr.Message('Select a customer before continuing.');
        CustomerErr.DetailedMessage('The current operation requires a valid Customer No.');
        Error(CustomerErr);
    end;
end;

Business Central supports passing an ErrorInfo instance to Error or Dialog.Error. Microsoft documents ErrorInfo as a structure for grouping information about an error and supporting richer error experiences. ErrorInfo and Dialog.Error

When should you use ErrorInfo?

3. TryFunction: catching AL errors

A method marked with the TryFunction attribute becomes a try method. When called as a Boolean expression, it returns true if the operation succeeds and false if an error occurs. The error is caught instead of immediately being shown to the user.

codeunit 50130 "Dynexal Try Function Demo"
{
    trigger OnRun()
    begin
        if TryValidateCustomer() then
            Message('Validation succeeded.')
        else
            Message('Validation failed.');
    end;

    [TryFunction]
    local procedure TryValidateCustomer()
    begin
        Error('Customer validation failed.');
    end;
}

Microsoft notes that the Boolean return value must actually be used for the call to behave as a try call. Simply calling a TryFunction method without consuming its return value does not make it a caught-error call. Handling errors using try methods

4. Important TryFunction transaction behavior

TryFunction is often misunderstood as an automatic database rollback mechanism. You should not treat it that way. Current Microsoft documentation states that database changes made by a try method are not rolled back. For Business Central online, write transactions can be present in try methods, while on-premises behavior is controlled by the Business Central Server configuration. Because of this, database writes inside try methods require careful design.

Important: do not assume that TryFunction means “all changes will be undone if something fails.” If a process needs reliable transaction boundaries, design those boundaries explicitly and understand where commits and writes occur.

For most integration scenarios, a good pattern is to keep the try method focused on an operation whose success or failure you want to detect, capture the error information, and then handle the result outside the try method.

5. GetLastErrorText()

When a caught error occurs, GetLastErrorText can return the last error message. It is particularly useful after a failed TryFunction or Codeunit.Run call.

ClearLastError();

if not TrySendRequest() then begin
    ErrorText := GetLastErrorText(true);
    Message('The operation failed: %1', ErrorText);
end;

The Boolean parameter can be used to exclude customer content from the returned error text. Microsoft documents this parameter as a way to exclude sensitive data such as primary key values. GetLastErrorText

6. GetLastErrorCallStack()

GetLastErrorCallStack returns the call stack associated with the last error. It is useful when you need to understand which procedures led to a failure.

ClearLastError();

if not TryProcessOrder() then begin
    ErrorText := GetLastErrorText(true);
    ErrorCallStack := GetLastErrorCallStack();
end;

For some error types, the returned call stack may not contain every call. Microsoft recommends using the debugger when you need the complete call stack for certain errors. GetLastErrorCallStack

7. Codeunit.Run() with a Boolean result

Another established pattern is to execute a codeunit and inspect its Boolean return value.

ClearLastError();

if not Codeunit.Run(Codeunit::"Dynexal Order Processor") then begin
    ErrorText := GetLastErrorText(true);
    Message('Order processing failed: %1', ErrorText);
end;

If the Boolean result is omitted and the codeunit fails, the error is raised normally. If the result is used, you can handle the failure in code. Microsoft also documents important transaction behavior for Codeunit.Run: when the Boolean return value is used, the transaction contained in the codeunit is always committed. Design this pattern carefully when database writes are involved. Codeunit.Run

8. Error handling for REST APIs and HttpClient

Integration code should distinguish between an HTTP request failing at the transport level and the remote API returning an unsuccessful HTTP status code. A useful pattern is to check HttpClient.Get or Send first, then inspect the response status and content.

procedure CallExternalApi(Url: Text): Boolean
var
    Client: HttpClient;
    Response: HttpResponseMessage;
    ResponseText: Text;
begin
    if not Client.Get(Url, Response) then begin
        Error('The external service could not be reached.');
    end;

    if not Response.IsSuccessStatusCode() then begin
        Response.Content().ReadAs(ResponseText);
        Error('External API returned status %1. Response: %2',
              Response.HttpStatusCode(), ResponseText);
    end;

    exit(true);
end;
Integration tip: do not expose secrets, access tokens or unnecessary customer data in error messages. Log only the diagnostic information that is appropriate for the environment.

For a deeper look at HTTP requests, headers, content and responses, see HttpClient in Business Central AL.

9. Handling JSON/API errors

External APIs often return JSON error objects. Do not assume every response has the same schema. Check the HTTP status first and then safely parse the response body.

if not Response.IsSuccessStatusCode() then begin
    Response.Content().ReadAs(ResponseText);
    // Parse known error properties only when they exist.
    Error('The API request failed with HTTP status %1.',
          Response.HttpStatusCode());
end;

For practical JSON parsing patterns using JsonObject, JsonArray, JsonToken and JsonValue, see JSON Handling in Business Central AL.

10. Collecting errors instead of stopping at the first one

Some processes need to validate many records and report several problems together instead of stopping at the first error. Business Central provides error collection features for scenarios such as bulk validation. This can be more user-friendly than raising one error, fixing it, rerunning the process and discovering the next problem.

Choose error collection when the business process benefits from a consolidated validation result. For a single blocking condition, a normal Error or ErrorInfo may be simpler.

11. Logging errors with telemetry

Production applications need more than user-facing messages. Telemetry can help developers understand failures that happened after the user left the page or when a background process failed.

Business Central exposes telemetry capabilities through the system telemetry framework. For example, the Feature Telemetry codeunit provides LogError overloads that can send error text, call-stack information and custom dimensions. Feature Telemetry

if not Success then begin
    FeatureTelemetry.LogError(
        '0000XYZ',
        'Dynexal Integration',
        'Customer synchronization',
        GetLastErrorText(true),
        GetLastErrorCallStack());
end;

Telemetry should contain useful technical context but should not become a dumping ground for sensitive customer information. Use appropriate data classification and avoid logging secrets such as access tokens.

12. A practical error-handling pattern for integrations

A robust integration can separate the operation, result handling and diagnostics.

procedure SyncCustomer(CustomerNo: Code[20]): Boolean
var
    ErrorText: Text;
    ErrorCallStack: Text;
begin
    ClearLastError();

    if not TrySyncCustomer(CustomerNo) then begin
        ErrorText := GetLastErrorText(true);
        ErrorCallStack := GetLastErrorCallStack();

        // Log technical details where appropriate.
        FeatureTelemetry.LogError(
            '0000CUST',
            'Dynexal Customer Sync',
            'Customer synchronization failed',
            ErrorText,
            ErrorCallStack);

        exit(false);
    end;

    exit(true);
end;

[TryFunction]
local procedure TrySyncCustomer(CustomerNo: Code[20])
begin
    // Call the external service and process the response.
    // Raise Error when the operation cannot continue.
end;

The exact architecture depends on whether the process is interactive, scheduled through a job queue, or part of a larger posting transaction. The important principle is to separate user-facing behavior from technical diagnostics.

13. Common error-handling mistakes

Using Message instead of Error

Message does not communicate a blocking failure in the same way as Error. If the process must stop because the data is invalid, use the appropriate error mechanism.

Showing raw technical errors to users

A raw HTTP response, SQL-style technical detail or call stack is usually not an appropriate user message. Give the user a concise explanation and keep technical diagnostics for logs or telemetry.

Assuming TryFunction rolls back everything

This is one of the most important mistakes. Current Microsoft documentation explicitly says database changes made by a try method are not rolled back. Design transaction behavior deliberately.

Calling a TryFunction without using its result

If the Boolean return value is not consumed, the call is not treated as a caught-error try call. Use the result in an if or assignment when you intend to catch the error.

Logging secrets

Never put passwords, OAuth access tokens, API keys or unnecessary personal/customer data into error messages or telemetry.

Ignoring the call stack

For production failures, error text alone may not identify the source. Capture a call stack when it adds useful diagnostic value.

14. Error handling best practices

15. Error handling checklist for API integrations

  1. Validate required configuration before making the request.
  2. Use secure authentication and never hard-code credentials.
  3. Handle transport/request failures.
  4. Check HTTP status codes.
  5. Safely read and parse error responses.
  6. Do not expose tokens or sensitive response data to users.
  7. Capture useful diagnostic details.
  8. Decide whether the operation should retry, skip, queue or stop.
  9. Log failures in a way that can be investigated later.
  10. Test timeout, authentication, validation, rate-limit and server-error scenarios.

16. Error handling vs debugging

Error handling decides what the application should do when something goes wrong. Debugging helps the developer discover why it went wrong. They work together but are not the same thing.

During development, use breakpoints and the AL debugger to inspect variables and call stacks. In production, telemetry and structured diagnostics can help investigate failures that cannot be reproduced interactively.

FAQ

Does TryFunction catch every possible error?

Try methods are designed to catch errors and exceptions that occur during AL execution. Their behavior has specific rules, so do not treat them as a universal replacement for all error-handling mechanisms. Review the current TryFunction documentation for your Business Central runtime.

Does TryFunction roll back database changes?

No. Current Microsoft documentation states that changes made by a try method are not rolled back. Transaction behavior should therefore be designed carefully.

When should I use Codeunit.Run?

Use it when a codeunit needs to be executed and you want to inspect its Boolean success/failure result. Be aware of the transaction semantics documented for the Boolean-returning form.

What is the difference between GetLastErrorText and GetLastErrorCallStack?

GetLastErrorText gives the last error message, while GetLastErrorCallStack gives the call stack associated with the last error.

Should I show GetLastErrorText directly to users?

Not automatically. The returned text may contain technical or sensitive details. Prefer a clear user-facing message and keep diagnostic details in appropriate logs or telemetry.

Related Dynexal tutorials

Final takeaway: good AL error handling is about making failures understandable, controllable and diagnosable. Use Error and ErrorInfo for clear application errors, TryFunction or Codeunit.Run when you need controlled failure handling, and telemetry when production diagnostics matter. Always design transaction behavior deliberately.