Salesforce Polymorphism: Apex, Polymorphic Fields and SOQL TYPEOF
A single reference can take different forms. See how this idea appears in Apex, relationship fields, and SOQL queries.
Polymorphism is one of those words that sounds more intimidating than the idea behind it. The core concept is simple:
Once that idea clicks, object-oriented code and Salesforce relationship fields become much easier to reason about.
Apex polymorphism: inheritance and method overriding
Imagine a universal remote with one power button. Point it at a television and the screen turns on. Point the same remote at a sound system and the speakers turn on. The command is the same; the concrete device determines the behavior.
In Apex, a variable declared with a general type can hold an instance of a more specific type. When an overridden method runs, Apex uses the behavior of the concrete object:
public class PolymorphismDemo {
public virtual class Animal {
public virtual String makeSound() {
return 'Generic sound';
}
}
public class Dog extends Animal {
public override String makeSound() {
return 'Woof';
}
}
public static void run() {
Animal animal = new Dog();
System.debug(animal.makeSound()); // Woof
}
}
The variable is typed as Animal, but the object is a Dog. That is why the overridden Dog implementation runs.
Polymorphic relationship fields in Salesforce
A conventional lookup is like a proprietary cable that fits one device type. A polymorphic field is closer to a USB port: one field can reference records from several allowed object types.
The important detail is that the field stores one reference per record. Its flexibility comes from the object type behind that ID. The field itself remains an ID; it does not become a decimal because an Opportunity has an Amount field.
TaskRelation records. The WhoId field still stores the primary reference, so "one ID per field" remains true even though it does not describe every activity relationship on its own.WhoId, WhatId, and OwnerId
These names are where many people first encounter polymorphism in Salesforce:
- Who: the person related to an activity, normally a Contact or Lead.
- What: the context related to the activity, such as an Account, Opportunity, Case, or another supported object.
- Owner: who owns the record. Depending on the object, this can reference a User or Group. A Queue is represented by a Group record, and not every object supports queue ownership.
This is a useful rule for Task and Event. The exact object types allowed can still vary by feature, target object, and organization configuration.
Querying the ID or navigating the relationship
With the Id suffix, such as WhoId, SOQL returns the identifier of the related record:
SELECT Id, WhoId, WhatId
FROM Task
Without the suffix, you navigate the relationship. A field shared across the possible types, such as Name, can be queried this way:
SELECT Id, Who.Name
FROM Task
You can also filter by the polymorphic relationship type:
SELECT Id, Who.Name
FROM Task
WHERE Who.Type = 'Lead'
This lets the database return only the records you need instead of loading a larger result set and filtering in Apex.
What.Industry or What.Amount as though every possible What object had those fields. Use TYPEOF for fields that belong to specific object types.SOQL TYPEOF: query fields by related object type
Opportunity has Amount, while Account has Industry. A polymorphic reference does not guarantee that every target object exposes the same fields.
TYPEOF works like a receptionist who asks different questions based on the visitor. When the related record is an Opportunity, request Amount and StageName. When it is an Account, request Industry.
SELECT TYPEOF What
WHEN Opportunity THEN Amount, StageName
WHEN Account THEN Industry
END
FROM Task
It resembles a type-aware switch inside SOQL. In suitable scenarios, it avoids separate queries and makes the data requirement explicit.
getSObjectType(): identify an ID without SOQL
Imagine Apex receives a collection of record IDs but not their object types. Querying records only to discover each type would consume SOQL unnecessarily.
getSObjectType() identifies the SObject type encoded in an ID without querying the database:
Id recordId = '00Q000000000001AAA';
Schema.SObjectType recordType = recordId.getSObjectType();
if (recordType == Lead.SObjectType) {
// Route to Lead logic
} else if (recordType == Contact.SObjectType) {
// Route to Contact logic
}
Call the method on an Id instance, as in recordId.getSObjectType(). The method does not load the record and does not consume a SOQL query. It returns the SObject type token associated with the ID prefix.
Summary to keep nearby
- Polymorphism: one reference, multiple forms.
- In Apex: a general reference can run the overridden behavior of the concrete object.
- In Salesforce data: a polymorphic field stores one ID per record and accepts specific supported target types.
- In SOQL: use
.Typefor filtering andTYPEOFfor type-specific fields. - In relationship naming: the
Idsuffix returns the reference; the base relationship name lets you navigate fields. - With an Apex ID: use
recordId.getSObjectType()to identify the type without SOQL.
A final question
Because flexibility also creates complexity. More restrictive relationships simplify validation, maintenance, query planning, and platform optimization. The right relationship depends on the real requirements of the data model.
Polymorphism is a scalpel, not a hammer.
Related Apex guide
Put your Apex examples to the test with test classes, assertions and code coverage.
Official sources for further study
Want to continue learning Salesforce and OmniStudio?
Explore practical content, OmniStudio 365, and my English OmniStudio lessons on YouTube.
Explore OmniStudio 365