What happens if you use database operations inside a TryFunction? In this video, I explore how TryFunctions changes standard AL behaviour. Check it out:

In this video, Erik explores whether performing database operations inside a TryFunction in AL is a good or bad idea. He walks through the history of error handling in Business Central—from the old Codeunit.Run approach to modern TryFunctions—and demonstrates exactly what happens to database transactions when errors occur inside a TryFunction. The key takeaway: errors inside a TryFunction do not roll back the current database transaction, which is a critical distinction from normal error behavior.
What Is a TryFunction?
Before diving into database operations, let’s recap what a TryFunction is. In AL, any procedure can be decorated with the [TryFunction] attribute. This implicitly changes the procedure’s return type to Boolean—it returns true if the procedure completes without error, and false if an error occurs. Crucially, the error is “caught” rather than surfaced to the user, and execution continues after the call.
Consider a simple example: dividing by zero. Without a TryFunction, this produces a runtime error that stops execution:
procedure test()
var
d, z : Decimal;
begin
d := 10 / z; // Runtime error: attempted to divide by zero
end;
But when you mark it as a TryFunction, you can handle the failure gracefully:
[TryFunction]
procedure test()
var
d, z : Decimal;
begin
d := 10 / z; // Error is caught, returns false
end;
// Calling code:
if not test() then
message('we could not divide by zero');
message('Success!');
The Old Way: Codeunit.Run
Before TryFunctions existed, the only way to catch errors in AL was to use Codeunit.Run. You would place your potentially failing code inside a codeunit’s OnRun trigger:
codeunit 50100 "Try"
{
trigger OnRun()
var
d: decimal;
z: Decimal;
begin
d := 10 / z;
end;
}
Then you’d call it with an if statement:
if Codeunit.Run(Codeunit::"Try") then
message('Success');
This worked, but had a significant limitation: Codeunit.Run can only be used when you’re in a clean commit state—meaning no uncommitted database writes have occurred. If you’ve done a Modify, Insert, or Delete before calling Codeunit.Run, you’ll get an error saying the call is not allowed.
This limitation made Codeunit.Run impractical in many real-world scenarios. Microsoft originally introduced it primarily to handle errors from external factors—things like .NET-based controls around authentication and CRM integrations—not for general-purpose AL error handling.
TryFunctions and Database Operations: The Key Behavior
This is where things get interesting—and potentially dangerous if you don’t understand the rules. Erik demonstrates this step by step.
Database Operations Before the TryFunction
First, Erik modifies a customer record before calling the TryFunction:
trigger OnOpenPage();
begin
Rec.FindFirst();
Rec.Name := 'Test 100';
Rec.Modify();
if not test() then
message('we could not divide by zero');
message('Success!');
end;
[TryFunction]
procedure test()
var
d, z : Decimal;
begin
d := 10 / z;
end;
The result: the error inside the TryFunction is caught, execution continues, and the database modification made before the TryFunction call remains intact. The open transaction is still valid.
Database Operations Inside the TryFunction
Now, Erik moves the database operation inside the TryFunction:
[TryFunction]
procedure test()
var
d, z : Decimal;
begin
Rec.FindFirst();
Rec.Name := 'Test 101';
Rec.Modify();
d := 10 / z;
end;
The result might surprise you: the customer’s name is changed to “Test 101” even though an error occurred after the modify. The database write is not rolled back.
This is the critical point: an error inside a TryFunction does not roll back the current database transaction. The error essentially behaves like an early exit—similar to calling exit(false). Whatever database operations completed before the error occurred remain part of the ongoing transaction.
Here’s the final version of the code Erik uses to demonstrate this:
namespace DefaultPublisher.TryFunctionDatabase;
using Microsoft.Sales.Customer;
pageextension 50100 CustomerListExt extends "Customer List"
{
trigger OnOpenPage();
begin
if not test() then
message('we could not divide by zero');
message('Success!');
end;
[TryFunction]
procedure test() // : Boolean
var
d, z : Decimal;
begin
Rec.FindFirst();
Rec.Name := 'Test 102';
Rec.Modify();
d := 10 / z;
end;
}
When this runs, “Test 102” is written to the database, the division by zero triggers the TryFunction to return false, and the message “we could not divide by zero” is displayed—but the record modification persists.
On-Prem vs. Cloud Behavior
There is an important difference between on-premises and cloud deployments. In Business Central online (cloud), database writes inside TryFunctions are allowed by default. On on-premises installations, you’ll get an error:
“A call to the function Modify is not allowed inside the call to page when it’s used as a try function.”
This is controlled by a server setting called “Enable Writes inside Try Functions” (or similar). On-prem, this defaults to false. If you want to allow database operations inside TryFunctions on your on-premises Business Central server, you need to explicitly set this to true.
This is precisely why Erik runs his demo against a cloud sandbox—to ensure he’s working with the default cloud behavior that most developers will encounter.
Debugger Behavior with TryFunctions
Erik also highlights a useful debugger setting. In your launch.json, you can control whether the debugger breaks on errors inside TryFunctions:
"breakOnError": "excludeTry"— The debugger will not break when an error occurs inside a TryFunction (recommended for normal development)."breakOnError": "all"— The debugger will break on all errors, including those inside TryFunctions. After stepping through, execution continues normally since the TryFunction catches the error.
Practical Use Cases for TryFunctions
Erik shares several real-world scenarios where TryFunctions shine:
Extended Error Information for APIs
In his advanced portal designer, Erik wraps large operations in a TryFunction. If an error occurs, the TryFunction catches it, and he can then grab stack traces and extended error information. This is especially valuable for API scenarios—normally, when an API call to Business Central fails, you only get a basic error message with no stack trace. By catching the error with a TryFunction and re-throwing it with extended information, you can provide much richer error diagnostics.
Input Validation / String Parsing
Instead of writing complex validation logic with numerous if statements to check every possible formatting issue in a string, you can take the “narrow path” approach: write your code assuming the input is correct, wrap it in a TryFunction, and let any parsing errors naturally surface as a simple “not formatted correctly” result. This keeps your code clean and avoids verbose defensive programming.
Summary
TryFunctions are a powerful and legitimate tool in AL development. The single most important thing to remember is:
- An error inside a TryFunction does NOT roll back the current database transaction. Database operations that completed before the error remain part of the active transaction.
- The error inside a TryFunction behaves like an early
exit(false)—it exits the procedure and returnsfalseto the caller. - On-premises Business Central disables database writes inside TryFunctions by default—you must enable a server setting to allow it.
- Cloud (SaaS) Business Central allows database writes inside TryFunctions by default.
- Use the
"breakOnError": "excludeTry"setting inlaunch.jsonfor a smoother debugging experience.
Should you use TryFunctions with database operations? Absolutely—just be fully aware that the normal “error = rollback” rule does not apply inside a TryFunction. Design your code accordingly, and TryFunctions become an incredibly useful tool for error handling, API diagnostics, input validation, and more.