Interfaces were introduced to Microsoft Dynamics 365 Business Central in version 2020 Release Wave 1 (BC 16). Their arrival marked a significant step toward improving extensibility, modular design, and decoupled architecture within AL development. Before interfaces existed, implementing flexible, interchangeable logic was difficult. Developers often relied on:
- large conditional statements (
IF…ELSEorCASEblocks), - events with cumbersome subscriber logic,
- SingleInstance codeunits acting as service containers,
- or inheritance-like patterns (which AL does not support).
These approaches worked, but they lacked true polymorphism and made code harder to maintain, test, and extend—especially in multi-extension environments.
Interfaces solved these issues by enabling:
- polymorphism (different objects sharing the same contract),
- clean separation of concerns,
- loose coupling,
- easy extensibility (especially for ISVs),
- clean testing via mock implementations.
Understanding interfaces is essential for any modern BC developer. Let’s explore them using the simplest possible example.
Interface Example: Vehicle → Car
To help someone encountering interfaces for the first time, we’ll start with the smallest example:
- one interface: IVehicle
- one implementation: Car
- a simple codeunit calling the interface in
OnRun
Because there is only one implementation, Business Central can automatically resolve it.
Step 1: Creating the Interface
interface IVehicle
{
procedure Drive();
}This defines a contract:
Every vehicle must be able to drive.
Step 2: Implementing the Interface (Car)
codeunit 90001 Car implements IVehicle
{
procedure Drive()
begin
Message('The car is driving.');
end;
}This is a concrete implementation of the interface.
Step 3: Using the Interface in a Codeunit
codeunit 90002 VehicleDemo
{
trigger OnRun()
var
Vehicle: IVehicle;
begin
Vehicle.Drive();
end;
}Why does this work?
Because there is only one implementation of IVehicle, Business Central automatically injects Car behind the scenes.
This is the simplest and most beginner-friendly demonstration of interfaces in BC.
Adding a Second Implementation: Bike
Now we introduce a second implementation to show what happens and how to solve it.
Step 4: Creating the Bike Implementation
codeunit 90003 Bike implements IVehicle
{
procedure Drive()
begin
Message('The bike is being pedaled.');
end;
}Now we have:
CarimplementsIVehicleBikeimplementsIVehicle
What Happens Now? Compilation Error
If we run our original codeunit:
Vehicle.Drive();The system no longer knows which implementation to pick.
Multiple implementations exist, and AL cannot choose automatically.
You will get a compilation error similar to:
Multiple implementations found for interface IVehicle. Specify one explicitly.
This is expected behavior.
How to Solve the Multiple-Implementations Problem
There are three common solutions.
Solution 1 — Explicit Assignment (Basic Approach)
Simply tell AL which implementation to use:
Vehicle := Codeunit Car;
Vehicle.Drive();or:
Vehicle := Codeunit Bike;
Vehicle.Drive();This works, but it couples the code to a specific implementation.
It’s a basic solution—for learning, not for production.
Solution 2 — Using Enum-Based Implementations (Best Practice)
This is the recommended approach and the one Microsoft uses internally for:
- email providers,
- address providers,
- payment services,
- API handlers,
- notifications, etc.
Step 1: Create Enum Implementing the Interface
enum 90000 VehicleType implements IVehicle
{
Extensible = true;
value(0; Car)
{
Implementation = IVehicle = Car;
}
value(1; Bike)
{
Implementation = IVehicle = Bike;
}
}We can call it “Eum-based Polymorphizm”, this is not an official name but it describe whole meaning.
Step 2: Use It in Code
Vehicle := VehicleType::Bike;
Vehicle.Drive();Business Central now resolves the correct implementation based on the enum value.
This is the cleanest and most scalable solution.
Solution 3 — Service Provider Pattern (Advanced)
You can store which implementation is active in:
- setup tables,
- SingleInstance codeunits,
- or external configuration.
Example:
Vehicle := VehicleProvider.GetCurrentVehicle();This is not Dependency Injection
Next example demonstrates a clean, native way of implementing Dependency Injection–like behavior in Business Central using interfaces and enum-based implementation binding.
1. Creating the Interface
We start by defining a simple interface that describes what a notification service must be able to do.
interface INotificationService{
procedure SendNotification(Recipient: Text; Message: Text);
}This interface defines a contract:
Any notification service must provide a
SendNotificationmethod.
It does not specify how the notification is sent—that is the responsibility of each implementation.
2. Implementing the Interface: EmailNotification
Next, we create the first implementation using an AL codeunit.
codeunit 80000 EmailNotification implements INotificationService
{
procedure SendNotification(Recipient: Text; Message: Text)
begin
Message('Email to %1: %2', Recipient, Message);
end;
}This codeunit fulfills the interface contract and simulates sending an email.
3. Implementing the Interface: SMSNotification
We now add a second implementation:
codeunit 80001 SMSNotification implements INotificationService
{
procedure SendNotification(Recipient: Text; Message: Text)
begin
Message('SMS to %1: %2', Recipient, Message);
end;
}This codeunit represents sending an SMS message.
4. Creating an Enum That Selects the Implementation
This is the key part of the example.
In Business Central, an enum can implement an interface by mapping each enum value to a specific implementation.
This allows AL to automatically select the correct codeunit at runtime.
enum 80000 NotificationMethod implements INotificationService
{
Extensible = true;
value(0; Email)
{
Implementation = INotificationService = EmailNotification;
}
value(1; SMS)
{
Implementation = INotificationService = SMSNotification;
}
}What this means:
NotificationMethod::Email→ uses theEmailNotificationimplementationNotificationMethod::SMS→ uses theSMSNotificationimplementation
This is a native AL mechanism for clean, safe dependency resolution.
5. Consuming the Interface Through the Enum
Now we write a codeunit that uses the enum to determine which implementation should be executed.
codeunit 80010 NotificationServiceRunner
{
procedure Notify(Method: NotificationMethod; Recipient: Text; TextToSend: Text)
var
Notifier: Interface INotificationService;
begin
// Resolve implementation from enum
Notifier := Method;
// Execute the notification
Notifier.SendNotification(Recipient, TextToSend);
end;
}How this works:
- The parameter
Methoddetermines the desired notification type. - When we assign
Notifier := Method;
Business Central automatically instantiates the correct implementation. - The codeunit then calls the interface method
SendNotification, which is executed by either:EmailNotification, orSMSNotification
depending on the enum value.
There is no need for IF statements, factories, or explicit selection logic—the enum does it all.
6. Example of Using the Notification Runner
In practice, you might call the runner like this:
var
Runner: Codeunit NotificationServiceRunner;
begin
Runner.Notify(NotificationMethod::SMS, '555-123', 'Your package has shipped.');
end;This would trigger the SMSNotification implementation automatically.
7. Why This Pattern makes sense
Using interfaces with enum-based implementation binding provides:
✔ Clean separation of logic
You code against an interface, never against concrete classes.
✔ Easy extensibility
Partners or other extensions can extend the enum and add new notification methods without modifying your code.
✔ Built-in dependency resolution
The AL runtime resolves implementations automatically.
✔ Testability
Mock implementations can be wired up simply by extending the enum.
✔ A natural way to model business rules
This pattern mirrors real-world choose a method scenarios (email, SMS, push, webhook, etc.).
Conclusion
Interfaces in Business Central brought true polymorphism and modular design to AL development. They:
- reduce coupling,
- increase flexibility,
- make extensions more maintainable,
- allow multiple interchangeable implementations,
- and support clean architecture patterns.
Using the Vehicle example is the perfect way for beginners to understand the basics and the NotificationService shows how to use it in practice.
This foundational pattern is the basis for many advanced Business Central features and real-world extension designs.
