If you have ever transitioned from Java, C#, or other object-oriented languages to Salesforce development, one of the first things you likely looked for was the ability to define custom generic classes. In many languages, generics allow you to write code that is decoupled from specific data types, enabling high levels of reusability and type safety.

In Salesforce Apex, the story of generics is a bit more nuanced. While Apex is built on top of Java-like syntax, it does not currently allow developers to define their own custom generic classes (e.g., public class MyWrapper<T>). However, that doesn't mean generics are entirely absent from the platform. From built-in collections to specific system interfaces, understanding how to leverage the existing "generic-ish" features of Apex is essential for writing clean, scalable code.

In this guide, we will explore the current state of generics in Apex, how to use parameterized interfaces, and the best workarounds for achieving generic-like behavior in your Salesforce applications.

Understanding Generics in the Salesforce Ecosystem

In the context of generic programming, "generics" refer to the ability to define a class, interface, or method with placeholders for the types they store or manipulate. These types are then specified when the code is instantiated or called. This provides two primary benefits: code reusability and compile-time type safety.

Apex supports generics in a "partially supported" capacity. This means that while the Salesforce engine uses generics extensively for its own system classes, it restricts you from defining your own generic signatures. You encounter generics every day in Apex through three main avenues:

  1. Collections: List<T>, Set<T>, and Map<K, V>
  2. Batch Apex: The Database.Batchable<sObject> interface
  3. System Interfaces: Interfaces like Iterator<T> and Comparable

Built-in Generic Types: Lists, Sets, and Maps

The most common implementation of generics in Apex is found in the Collection classes. Before generics were introduced to modern programming, collections often held a generic "Object" type, which required constant casting and was prone to runtime errors.

Consider a scenario without generics:

// Hypothetical non-generic list
List contacts = new List();
contacts.add(new Contact());
contacts.add(new Account()); // This would be allowed, leading to errors later

With the generic implementation of List<T>, Apex provides compile-time type safety. This ensures that your collection only contains the specific data types you intended:

List<Contact> contacts = new List<Contact>();
contacts.add(new Contact());

// The following line will cause a compile-time error
// contacts.add(new Account()); 

By using the <T> syntax (where T represents a Type), the Apex compiler can validate that the elements being added to the list match the definition. This prevents the dreaded System.TypeException at runtime.

Implementing Parameterized Interfaces

While you cannot create a custom class with a generic signature, Salesforce does allow you to implement certain system interfaces that use parameterized typing. This is often where developers get confused. You can write a class that fulfills a generic contract defined by Salesforce.

A classic example is the Iterator<T> interface. If you want to create a custom iterator for a specific type, you can define it like this:

global class ListIterator implements Iterator<Contact> {
    private List<Contact> contacts;
    private Integer currentIndex;

    global ListIterator(List<Contact> contacts) {
        this.contacts = contacts;
        this.currentIndex = 0;
    }

    global boolean hasNext() { 
        return currentIndex < contacts.size();
    }    

    global Contact next() {        
        if (hasNext()) {
            return contacts[currentIndex++];
        } else {
            throw new NoSuchElementException();
        }
    }
}

In this example, Iterator<T> is the generic interface provided by Salesforce. By implementing Iterator<Contact>, you are telling the compiler that the next() method must return a Contact object. This provides the type safety you need without having to cast an Object back to a Contact every time you iterate.

The "Object" Workaround: Achieving Runtime Flexibility

Since we cannot define a class as MyClass<T>, how do we build reusable components that work with any data type? The most common pattern in Apex is using the Object class. In Apex, every class (including sObjects and user-defined classes) inherits from Object.

If you need to create a utility that handles various types, you can pass and return Object types, though you lose compile-time safety. You will need to cast the result back to the desired type at runtime.

public class GenericWrapper {
    private Object data;

    public GenericWrapper(Object input) {
        this.data = input;
    }

    public Object getData() {
        return this.data;
    }
}

// Usage
GenericWrapper wrap = new GenericWrapper(new Account(Name = 'Test'));
Account acc = (Account)wrap.getData(); // Manual casting required

While this approach works, it is "brittle." If you accidentally pass an Opportunity into the wrapper but try to cast it to an Account, your code will crash at runtime. This is why many developers have advocated for true custom generics on the Salesforce IdeaExchange.

Dynamic Apex and SObjects

If your goal is to write generic code specifically for database records, you should leverage the SObject type rather than Object. The SObject class is the generic base class for all Salesforce objects (Account, Contact, Custom_Object__c, etc.).

Using SObject allows you to access common methods like get(), put(), and getSObjectType(), which are incredibly powerful for building generic triggers or service layers.

public void processRecords(List<SObject> records) {
    for (SObject so : records) {
        // You can access fields dynamically without knowing the object type
        System.debug('Processing record: ' + so.get('Id'));
    }
}

By combining SObject with the Type class (e.g., Type.forName('Account')), you can instantiate objects and call methods dynamically at runtime, mimicking much of the behavior found in generic programming.

Frequently Asked Questions

Can I create a custom class like public class MyClass<T> in Apex?

No. Apex does not currently support user-defined generic types. You can only use the generic syntax with built-in system types like List, Set, Map, and specific interfaces like Batchable or Iterator.

Why does Salesforce limit the use of generics?

Salesforce uses a multi-tenant architecture where code is compiled and executed in a shared environment. The restrictions on generics are likely in place to simplify the compiler and ensure platform stability and performance across millions of orgs.

Is there a performance penalty for using the Object workaround?

There is a negligible performance cost for casting types at runtime. However, the primary cost is "developer overhead"—the increased risk of runtime errors and the need for more unit tests to ensure type safety.

Wrapping Up

While Apex doesn't offer the full suite of generic programming features found in other languages, it provides enough tools to build highly reusable and dynamic applications. By mastering List<T>, implementing parameterized interfaces like Iterator<T>, and utilizing the SObject base class, you can write sophisticated code that handles a variety of data types efficiently.

As the platform evolves, keep an eye on the official Salesforce Release Notes. While the current implementation has remained stable since the Winter '13 updates, the developer community continues to push for expanded generic support to make Apex even more powerful.