AL Development

How to Use Default Implementations in AL Interfaces

Add methods with a default body to AL interfaces in Business Central 2026 release wave 2, so you can extend a published interface without breaking implementations, and phase methods to required with RequiredPending.

8 min read

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

  1. Business Central 2026 release wave 2 (version 29) or later as the target platform.
  2. The AL Language extension for Visual Studio Code that ships with version 29.
  3. runtime set to 18.0 or later in app.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 0 or 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 Interface variable first.
  • Removing a default body directly. Go through [RequiredPending] first, or AppSourceCop AS0148 stops the build.
  • Forgetting the runtime. Without runtime 18.0 in app.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.