Understanding the nuances of Object-Oriented Programming (OOP) is essential for any Salesforce developer looking to build scalable, maintainable applications. Two keywords that often cause confusion are abstract and virtual. While they both facilitate inheritance, they serve distinct purposes in your codebase.

In this guide, you will learn the fundamental differences between abstract and virtual classes, see practical code examples, and understand the architectural reasons for choosing one over the other. By the end, you'll be able to design more robust Apex frameworks that follow professional software engineering principles.

What is a Virtual Class in Apex?

A virtual class is a "full" class that provides a base implementation of logic. It can be instantiated directly using the new keyword, but it also permits other classes to extend it.

When you mark a class as virtual, you are essentially saying, "This class works on its own, but feel free to change its behavior if you need to." To allow a child class to change a specific method's logic, you must also mark that individual method as virtual.

Virtual Class Example

public virtual class DiscountProvider {
    public virtual Decimal calculateDiscount(Decimal amount) {
        // Default implementation: 5% discount
        return amount * 0.05;
    }
}

// Usage
DiscountProvider dp = new DiscountProvider(); // This works!

In this scenario, a child class could extend DiscountProvider and override the calculateDiscount method to provide a 10% discount for VIP customers, while other parts of the system continue to use the default 5% logic.

What is an Abstract Class in Apex?

An abstract class is a "partial" class. It acts as a blueprint or a contract for other classes. Unlike virtual classes, you cannot instantiate an abstract class directly. If you try to call new MyAbstractClass(), the compiler will throw an error.

Abstract classes are powerful because they can contain abstract methods—methods that have a signature (name and parameters) but no body. Any non-abstract class that extends an abstract class must provide a concrete implementation for these abstract methods.

Abstract Class Example

public abstract class BaseIntegration {
    // A concrete method with shared logic
    public void logTransaction(String status) {
        System.debug('Transaction status: ' + status);
    }

    // An abstract method: Child classes MUST implement this
    public abstract void executeCallout();
}

// Usage
// BaseIntegration bi = new BaseIntegration(); // This would fail to compile!

Key Differences at a Glance

To help you decide which to use, here is a breakdown of how these two modifiers compare across several technical dimensions:

Feature Abstract Class Virtual Class
Instantiation Cannot be initialized directly (new fails) Can be initialized directly
Method Implementation Can have methods with no body (abstract) All methods must have a body
Enforcement Forces child classes to implement abstract methods Cannot force child classes to implement anything
Purpose To provide a partial template/contract To provide a base implementation that can be overridden
Method Overriding Only methods marked abstract or virtual can be overridden Only methods marked virtual can be overridden

When to Use Abstract vs. Virtual

Choosing the right modifier depends on the architectural "contract" you want to establish with other developers (or your future self).

Use an Abstract Class when:

  1. You have a partial algorithm: You have 200 lines of common code (like an AWS authentication signature) but the final 10 lines of the request vary by service.
  2. You want to enforce a contract: You want to ensure that every developer who creates a "PaymentProcessor" class implements a processPayment() method.
  3. The base class is incomplete: It doesn't make sense for the base class to exist on its own (e.g., a generic Shape class versus a specific Square class).

Use a Virtual Class when:

  1. Providing default behavior: You have a Trigger Handler framework where most triggers use the same basic logic, but you want to allow specific objects to override the afterUpdate behavior.
  2. Large "Full" Classes: You have a complex utility class that works perfectly for 90% of use cases but needs slight customization for the remaining 10%.

Common Pitfalls to Avoid

  • Forgetting the override keyword: In Apex, if you are extending a class and redefining a method, you must use the override keyword in the child class. Without it, your code will not compile.
  • Over-complicating Simple Logic: Don't use inheritance if a simple utility class or helper method will suffice. Inheritance creates tight coupling between classes.
  • Abstract Methods in Virtual Classes: You cannot have an abstract method inside a virtual class. If you need an abstract method, the entire class must be marked abstract.

Frequently Asked Questions

Can an abstract class have a constructor?

Yes, an abstract class can have a constructor. While you cannot call new MyAbstractClass(), the constructor of the abstract class is called when a child class is instantiated via super().

Can a class implement multiple abstract classes?

No. In Apex, a class can only extend one other class (whether it is abstract or virtual). If you need to enforce multiple contracts, you should use Interfaces instead.

What is the difference between an Interface and an Abstract Class?

An Interface is a 100% abstract contract; it cannot contain any logic or variables. An Abstract Class is a mix—it can contain implemented methods, member variables, and abstract methods. Use an Interface when you want to define a capability (e.g., isSearchable), and use an Abstract Class when you want to share code logic.

Wrapping Up

The choice between abstract and virtual comes down to the level of flexibility versus enforcement you require.

  • Use Abstract when you want to provide a template and force specific implementations.
  • Use Virtual when you want to provide a working solution that allows for optional customization.

By relying on these contracts rather than rigid implementations, your Salesforce code becomes more resistant to change and significantly easier to test. Next time you build a service layer or a trigger framework, consider if an abstract base class could help standardize your team's approach.