Until version 29, adding a method to a published AL interface was a breaking change. Every codeunit that implemented the interface stopped compiling until someone added the new method, including codeunits in other publishers’ apps. AppSourceCop rule AS0066 exists for exactly this reason.
Business Central 2026 release wave 2 (runtime 18.0) lets interface methods have a body. A method with a body is a default implementation: codeunits can override it, but they don’t have to. This post shows the syntax, how calls are dispatched, and how to turn a default method into a required one later without breaking anyone.
Prerequisites
- Business Central 2026 release wave 2 (version 29) or later as the target platform.
- The AL Language extension for Visual Studio Code that ships with version 29.
runtimeset to18.0or later inapp.json.
{
"platform": "29.0.0.0",
"application": "29.0.0.0",
"runtime": "18.0"
}
If your app also supports older versions, you can’t use default methods until you drop support for them, because older runtimes don’t understand an interface method with a body.
Steps
1. Start from an existing interface
Take a payment method interface that has shipped and has implementations in several apps:
interface IPaymentMethod
{
procedure ValidateAmount(Amount: Decimal): Boolean;
procedure GetDescription(): Text;
}
2. Add a method with a default body
You now need a transaction fee per payment method. Most methods have no fee, so a default of zero fits:
interface IPaymentMethod
{
procedure ValidateAmount(Amount: Decimal): Boolean;
procedure GetDescription(): Text;
// Default implementation: implementors can omit or override it
procedure GetTransactionFee(): Decimal
begin
exit(0);
end;
}
Methods without a body stay required. Methods with a body are optional.
Adding a default method doesn’t change the interface’s runtime identifier, which is computed from the required methods only. Dependent extensions keep working without being recompiled.
3. Implement the interface
Existing codeunits compile unchanged and inherit the default:
codeunit 50100 "Cash Payment" implements IPaymentMethod
{
procedure ValidateAmount(Amount: Decimal): Boolean
begin
exit(Amount > 0);
end;
procedure GetDescription(): Text
begin
exit('Cash');
end;
// GetTransactionFee is inherited from the interface default
}
A codeunit that needs different behavior overrides the method by declaring it:
codeunit 50101 "Card Payment" implements IPaymentMethod
{
procedure ValidateAmount(Amount: Decimal): Boolean
begin
exit((Amount > 0) and (Amount <= 50000));
end;
procedure GetDescription(): Text
begin
exit('Credit card');
end;
procedure GetTransactionFee(): Decimal
begin
exit(2.5);
end;
}
4. Call default methods through an interface variable
Default methods are dispatched through the interface, not through the codeunit’s own methods. This works:
procedure ShowFee()
var
PaymentMethod: Interface IPaymentMethod;
CashPayment: Codeunit "Cash Payment";
begin
PaymentMethod := CashPayment;
Message('Fee: %1', PaymentMethod.GetTransactionFee()); // 0, from the default
end;
This doesn’t compile, because Cash Payment doesn’t declare GetTransactionFee:
procedure ShowFeeWrong()
var
CashPayment: Codeunit "Cash Payment";
begin
Message('Fee: %1', CashPayment.GetTransactionFee()); // compile error
end;
In practice this is rarely a problem, since code that works with interfaces already uses interface variables, typically assigned from an enum:
enum 50100 "Payment Method Type" implements IPaymentMethod
{
Extensible = true;
value(0; Cash) { Implementation = IPaymentMethod = "Cash Payment"; }
value(1; Card) { Implementation = IPaymentMethod = "Card Payment"; }
}
procedure CalcTotal(MethodType: Enum "Payment Method Type"; Amount: Decimal): Decimal
var
PaymentMethod: Interface IPaymentMethod;
begin
PaymentMethod := MethodType;
if not PaymentMethod.ValidateAmount(Amount) then
Error('Amount %1 is not valid for %2.', Amount, PaymentMethod.GetDescription());
exit(Amount + PaymentMethod.GetTransactionFee());
end;
5. Plan the move from default to required
Sometimes a default is only a bridge. Suppose every payment method must eventually report its own fee. Removing the body in the next release would break every implementation that relies on the default, and AppSourceCop flags that as error AS0148.
Use the [RequiredPending] attribute first:
interface IPaymentMethod
{
procedure ValidateAmount(Amount: Decimal): Boolean;
procedure GetDescription(): Text;
[RequiredPending('Implement GetTransactionFee. The default will be removed in version 31.0.', '31.0')]
procedure GetTransactionFee(): Decimal
begin
exit(0);
end;
}
Codeunits that rely on the default now get compiler warning AL0924 with your message. The version parameter is informational only and doesn’t enforce anything.
The full lifecycle has three phases:
| Phase | Interface change | Effect on implementors |
|---|---|---|
| 1 | Add the method with a default body | Nothing. Existing code compiles and uses the default. |
| 2 | Add [RequiredPending] |
Warning AL0924 on codeunits that use the default |
| 3 | In a later major version, remove the body and the attribute | The method is required. AppSourceCop AS0149 warns about the runtime ID change. |
Give partners at least one major release between phase 2 and phase 3.
6. Find implementations
Go to Implementations (Ctrl + F12) on an overriding method now also lists the default method on the interface, next to the explicit implementations in other codeunits. Use it before you change a default to see who relies on it.
Common Mistakes
- Using defaults for behavior that every implementation must define. A default of
0or an empty text can hide a missing implementation until it shows up as wrong data. Make the method required, or use[RequiredPending]from the start. - Calling the method on the codeunit variable. Inherited defaults only exist on the interface. Assign the codeunit to an
Interfacevariable first. - Removing a default body directly. Go through
[RequiredPending]first, or AppSourceCop AS0148 stops the build. - Forgetting the runtime. Without
runtime18.0 inapp.json, the compiler rejects a method body in an interface.
Next Steps
Look through your published interfaces for places where you avoided adding a method because of the breaking change. Those are the first candidates for a default. If you extend interfaces with extends today, default methods often make the extra interface unnecessary for small additions.
Interfaces and events solve related problems in different ways. For the event side, see How to Use Events and Subscribers in Business Central AL Development.