In this video, I show how to use a [TryFunction] to catch errors that otherwise would stop the execution of AL code. TryFunctions are easy to use and make handling errors more flexible and powerful.

In this video, Erik explores the TryFunction attribute in AL — a powerful error handling mechanism in Business Central that lets you catch errors in code and continue execution gracefully, rather than having the entire operation roll back to the last stable point.
Default Error Handling in Business Central
Normally in Business Central, when an error occurs — whether it’s a runtime error or an explicit Error statement — execution stops completely. The system rolls back whatever transaction is open and returns to the last stable user interface anchor point. If you’re on a page and run something that fails, you end up back on that page. If a report fails, you go back to where the report was launched from.
But sometimes you need to catch an error in code and handle it gracefully so your logic can continue running.
The Old Way: CODEUNIT.RUN
Back in the day, the only proper way to catch errors was using the CODEUNIT.RUN statement wrapped in an IF expression. This let you catch errors that happened within the scope of the codeunit you ran. However, this meant that whenever you wanted to catch something, you needed to encapsulate that logic into a separate codeunit — which created challenges around handling return values and data flow. It worked fine for big operations like “try to post, and handle it if posting fails,” but for smaller, more granular error handling, it didn’t work well at all.
Setting Up the Problem: JSON Parsing
Erik demonstrates a real-world scenario that came up at one of his customers involving JSON parsing. He starts by building a simple JSON object:
var
J: JsonObject;
T: JsonToken;
D: Decimal;
begin
J.Add('amount', 1000);
end;
Using Format on the JSON object, you can see the resulting JSON structure: {"amount":1000}. To extract the value, you use a JsonToken — the catch-all type that all JSON elements resolve to. Depending on the kind of token, it can relate to a JsonObject, a JsonArray, or a JsonValue.
With a numeric value in the amount field, converting the token to a decimal works perfectly:
if J.Get('amount', T) then begin
D := T.AsValue().AsDecimal();
Message('The Amount is %1', D);
end;
This outputs “The Amount is 1,000” — a properly formatted decimal. Everything works beautifully.
When JSON Gets Messy
The problem with JSON is that there’s no type definition, no schema enforcement. What happens when the source system sends something unexpected — like a string value "not available" or a malformed number like "1.00.00" in the amount field?
You get a nasty error: “Unable to convert from Microsoft.Dynamics.Nav.Runtime.JsonValue to Microsoft.Dynamics.Nav.Runtime.Decimal18.” Erik notes the error message is a bit sloppy — Decimal18 is the internal representation, not what the developer is actually working with.
This is where the TryFunction comes in.
Introducing the TryFunction Attribute
The solution is to extract the risky conversion code into its own procedure and decorate it with the [TryFunction] attribute. Think of it as similar to a try/catch block in C#:
// C# equivalent concept:
try {
// some code that might fail
}
catch {
// handle the error
}
In AL, you achieve the same thing, but with a different syntax. Here’s the final working code:
pageextension 50104 CustomerListExt extends "Customer List"
{
trigger OnOpenPage()
var
J: JsonObject;
T: JsonToken;
D: Decimal;
begin
J.Add('amount', '1.00.00');
if J.GET('amount', T) then begin
if TryDecimal(T, D) then
message('The Amount is %1', D)
else
message('That''s not a number! (%1)', GetLastErrorText());
end;
end;
[TryFunction]
local procedure TryDecimal(var T: JsonToken; var D: Decimal)
begin
D := T.AsValue().AsDecimal();
end;
}
How TryFunction Changes a Procedure’s Behavior
When you apply the [TryFunction] attribute to a procedure, several important things happen:
- The return type changes implicitly to Boolean. You cannot add your own return value — the compiler enforces this. If you try to add a return type, you’ll get an error: “The signature of procedure does not match the signature required by attribute TryFunction.”
- The function returns
trueif execution completes successfully, andfalseif an error occurs inside it. - Errors are swallowed — instead of stopping execution and rolling back, control returns to the caller with a
falseresult. - You can retrieve the error message using
GetLastErrorText()to understand what went wrong.
Erik demonstrates this with the debugger: stepping through the code, you can see execution enter TryDecimal, fail on the conversion, and then return to the else branch — where the message “That’s not a number!” is displayed along with the actual error text from GetLastErrorText().
Important Limitations
Erik highlights a critical limitation: don’t perform database operations inside a TryFunction. Database writes within a TryFunction won’t work as expected because the error handling mechanism interacts with transaction management. TryFunctions are best suited for:
- Handling web service calls that might fail
- Parsing and converting data from external sources (like JSON)
- Validating complex values (dates, numbers, formats) where writing manual validation code would be cumbersome
Practical Advice
Rather than trying to write extensive validation logic to check if a value is a valid number, date, or other complex type before converting it, Erik recommends a simpler approach: encapsulate your normal conversion code inside a TryFunction and handle the error if it fails. In many cases, this is far easier than trying to anticipate every possible invalid input format.
Summary
The [TryFunction] attribute in AL provides a clean way to handle errors without stopping execution — similar in concept to try/catch blocks in languages like C#. It implicitly converts a procedure into a Boolean function that returns true on success and false on failure. Combined with GetLastErrorText(), it gives you full visibility into what went wrong. Just remember to keep database operations out of TryFunctions, and use them for scenarios like external data parsing, web service calls, and type conversions where failures are expected and need to be handled gracefully.