Business Central AL Error Handling: ErrorInfo, TryFunction & Production Patterns
Error handling is more than writing Error('Something went wrong.'). In a production Business Central extension, an error should help the user understand what failed, help the developer diagnose the problem, and—when appropriate—offer a safe way to continue or fix it.
ErrorInfo when you need richer and potentially actionable error information, and TryFunction when you deliberately want to catch an error and handle the failure path.1. The three questions every error should answer
- What failed? Give the business user a meaningful message.
- Why did it fail? Preserve useful technical context without exposing secrets.
- What can be done next? Where appropriate, provide a clear correction or action.
Microsoft's AL documentation treats error handling as a distinct development area, alongside debugging and diagnostics.
2. Basic Error() versus structured error information
The traditional approach is useful when the operation simply cannot continue:
if Customer."No." = '' then
Error('Customer number is required.');
This is simple, but complex applications often need more context than a single message can provide.
3. ErrorInfo: richer error context
ErrorInfo represents structured information about an error. It can be used to provide a message and additional information for the error experience. Microsoft also documents properties such as page navigation and custom dimensions for error-related actions and handling.
var
MyErrorInfo: ErrorInfo;
begin
MyErrorInfo.Message('Customer setup is incomplete.');
MyErrorInfo.DetailedMessage(
'Configure the customer posting setup before posting the document.');
Error(MyErrorInfo);
end;
The exact properties available depend on the target Business Central runtime, so compile the pattern against the symbols for the version you support.
4. Why detailed errors should not expose secrets
Detailed diagnostics are useful, but they should never become a place to dump access tokens, passwords, authorization headers or other sensitive values.
// ❌ Never expose credentials
MyErrorInfo.DetailedMessage(
'API failed. Token=' + AccessToken);
// ✅ Safer
MyErrorInfo.DetailedMessage(
'API authentication failed. Check the integration credential.');
For integration failures, a safe HTTP status, endpoint name, correlation identifier or provider error code is generally more useful than the credential itself.
5. TryFunction: when you intentionally catch an error
A method marked with [TryFunction] can return a Boolean result. When the return value is used, an error inside the try method is caught and the call evaluates to false. Microsoft documents this behavior and also notes important transaction considerations.
[TryFunction]
local procedure TryValidateCustomer(CustomerNo: Code[20])
var
Customer: Record Customer;
begin
Customer.Get(CustomerNo);
if Customer.Blocked <> Customer.Blocked::" " then
Error('Customer is blocked.');
end;
procedure ValidateCustomer(CustomerNo: Code[20])
begin
if TryValidateCustomer(CustomerNo) then
exit;
Error('Customer validation failed.');
end;
6. A critical TryFunction transaction point
Do not treat a TryFunction as a transaction rollback mechanism. Microsoft states that database changes made by a try method aren't rolled back. For on-premises environments, write transactions inside try methods are restricted by default and can produce a runtime error unless the relevant server configuration is changed.
7. GetLastErrorText after a failed try call
When a try method fails, GetLastErrorText() can be used to retrieve the Business Central error text. Microsoft also documents GetLastErrorObject() for inspecting exception details in relevant scenarios.
if not TryCallExternalService() then begin
ErrorMessage := GetLastErrorText();
// Log safe diagnostic context if required.
Error(
'The external service could not be reached. %1',
ErrorMessage);
end;
Before exposing the returned text directly to users, review whether it contains implementation details or sensitive information.
8. TryFunction is not a replacement for validation
Do not use TryFunction for every business rule. If a condition can be checked clearly before executing the operation, explicit validation is often easier to read and maintain.
if SalesHeader."Sell-to Customer No." = '' then
Error('Sell-to Customer No. must be specified.');
if SalesHeader."Posting Date" = 0D then
Error('Posting Date must be specified.');
Use a try pattern when you genuinely need to handle an operation that may throw an error, especially when the failure path has a meaningful alternative.
9. Actionable errors
A richer error experience can include an action that takes the user to the relevant setup or page. Microsoft documents PageNo and navigation actions on ErrorInfo for this type of scenario.
var
SetupError: ErrorInfo;
begin
SetupError.Message('Integration setup is incomplete.');
SetupError.DetailedMessage(
'Configure the API endpoint and credential before retrying.');
SetupError.PageNo(Page::"My Integration Setup");
SetupError.AddNavigationAction('Open Integration Setup');
Error(SetupError);
end;
This pattern turns an error from a dead end into a guided correction path.
10. CustomDimensions for machine-readable context
ErrorInfo.CustomDimensions can carry additional key/value information associated with the error. Microsoft documents it as a way to transfer information between the code showing the error and code that handles collected errors or fix actions.
var
Dimensions: Dictionary of [Text, Text];
begin
Dimensions.Add('Integration', 'Shopify');
Dimensions.Add('Operation', 'CreateOrder');
MyErrorInfo.CustomDimensions(Dimensions);
end;
Keep custom dimensions safe. Do not put credentials, tokens or personal data into diagnostic fields without a clear and appropriate reason.
11. Multiple errors and validation scenarios
Some business processes have several independent validation problems. Instead of failing at the first problem, a design may collect multiple errors and present them together. This is especially useful for document validation where the user can fix several fields in one pass.
When implementing collected-error patterns, keep each message specific and actionable. The user should be able to identify the record or field that needs attention without reading internal implementation details.
12. Error handling in API integrations
API integrations have more than one failure layer:
- Transport: the request cannot be sent or completed.
- HTTP: the server returns 4xx or 5xx.
- Authentication: credentials or tokens are rejected.
- Payload: the request or response contains invalid data.
- Business: the remote system rejects a validly formatted request.
Handle these cases separately. A generic “API failed” message makes production support much harder.
13. Background jobs need a different error mindset
A Job Queue process has no user sitting at the page waiting to read a message. Errors should therefore be diagnosable from logs/telemetry and should leave the job in a state that operations staff can investigate.
For a background integration, consider recording safe identifiers such as operation type, external document number, HTTP status and retry state. Avoid repeatedly retrying permanent failures such as invalid credentials or malformed business data.
14. Debugging: don't guess from the message alone
When a production error is difficult to reproduce, use the available Business Central troubleshooting tools. Microsoft recommends profiling/debugging in appropriate environments and using telemetry to investigate issues after they occur.
- Reproduce in a sandbox where possible.
- Set a breakpoint around the failing operation.
- Inspect the call stack.
- Check event subscribers that participate in the operation.
- Review telemetry for errors that are difficult to reproduce.
- Compare the failing data with a successful transaction.
15. Error messages should be written for the user
Compare these two messages:
// Weak
Error('Error 500');
// Better
Error(
'Shopify order synchronization failed. ' +
'The external service returned HTTP 500. Try again later.');
The second message tells the user what operation failed and gives a reasonable next step without exposing internal code.
16. A production-ready pattern
procedure SyncOrder(OrderNo: Code[20])
begin
ValidateOrder(OrderNo);
if not TrySendOrder(OrderNo) then begin
LogSafeFailure(OrderNo, GetLastErrorText());
Error(
'Order %1 could not be synchronized. ' +
'Check the integration status and try again.',
OrderNo);
end;
MarkOrderAsSynchronized(OrderNo);
end;
The important architectural idea is separation: validation, external operation, safe diagnostics and business-state updates should not become one large procedure.
17. Common mistakes
- Using TryFunction as if it automatically rolled back database changes.
- Catching an error and silently ignoring it.
- Showing raw technical exceptions to business users.
- Putting tokens or passwords into detailed errors or logs.
- Using TryFunction where a simple validation would be clearer.
- Retrying permanent errors indefinitely.
- Giving background jobs no diagnostic context.
- Making every error message generic.
18. Interview-ready questions
- What is the difference between
Error()andErrorInfo? - How does
TryFunctionwork in AL? - Does TryFunction roll back database changes?
- When would you use
GetLastErrorText()? - What is an actionable error?
- Why would you use
CustomDimensionswith ErrorInfo? - How would you handle errors in a Job Queue integration?
- How do you prevent secrets from appearing in error messages?
19. Practical production checklist
- Validate predictable business conditions before expensive operations.
- Use ErrorInfo when structured or actionable error information adds value.
- Use TryFunction deliberately, not as a blanket wrapper around all code.
- Understand transaction behavior before placing database writes in try methods.
- Keep diagnostic context safe and useful.
- Separate transient integration failures from permanent business failures.
- Make background-process failures observable.
- Test both success and failure paths.
Conclusion
Good AL error handling is part of application design, not just defensive coding. The strongest implementations make failures understandable to users, diagnosable for developers and safe for production operations. ErrorInfo, actionable errors and carefully used TryFunction patterns give AL developers more control, while debugging and telemetry complete the operational side.