In Salesforce development, you will frequently need to pass complex data structures between your backend Apex logic and your frontend Lightning Web Components (LWC). Because a standard Salesforce SObject (like an Account or Contact) cannot hold custom, non-database variables on the fly, wrapper classes serve as the perfect vehicle. By grouping related data into one unit, you keep your code organized, scalable, and much easier to read.
Key Points
- Combines Heterogeneous Data: Mix standard objects, primitive data types (like Integers and Booleans), and collections in one place.
- Essential for UI Data Tables: Wrappers are commonly used to add an
isSelectedboolean to a record so users can select multiple rows in a Datatable. - Perfect for Integrations: When calling external APIs, wrapper classes are the standard way to map complex incoming JSON data into readable Apex objects.
Real-World Example: Building a Wrapper
Let's look at the most common scenario. Imagine you have a Lightning Web Component that displays a list of Accounts, and you want the user to be able to check a box next to them. The standard Account object does not have an "isSelected" field in your database. We solve this by wrapping the Account in a custom class.
public class AccountWrapper {
// Use @AuraEnabled so the Lightning Web Component can see these variables
@AuraEnabled
public Account accRecord { get; set; }
@AuraEnabled
public Boolean isSelected { get; set; }
// Constructor to easily create new instances
public AccountWrapper(Account acc, Boolean isSelected) {
this.accRecord = acc;
this.isSelected = isSelected;
}
}
Accessing Your Data
To use this class in your Apex controller, you simply create an instance of it, pass in the required data using your constructor, and then use standard "dot notation" to read or change the data.
// 1. Query an account
Account myAccount = [SELECT Id, Name FROM Account LIMIT 1];
// 2. Instantiate the wrapper
AccountWrapper wrapper = new AccountWrapper(myAccount, true);
// 3. Drill into the data using dot notation
System.debug('Account Name: ' + wrapper.accRecord.Name);
System.debug('Is Selected? ' + wrapper.isSelected);
Always remember to initialize your internal objects or lists before you attempt to assign values to them. If your wrapper class contains a
List<String>, you must declare it as new List<String>() in the constructor. Accessing or adding to a field on an uninitialized (null) object will crash your code with a NullPointerException.
Frequently Asked Questions
Standard SObjects (like Contact or Opportunity) strictly mirror the fields you have saved in your Salesforce database. You cannot add a temporary "status" string or an "isSelected" boolean to an SObject in Apex memory. A wrapper class gives you the freedom to attach temporary properties to a record without creating new custom fields in your database.
It is incredibly simple. Annotate the variables inside your wrapper class with @AuraEnabled (as shown in the code example above). Then, write an @AuraEnabled(cacheable=true) method in your main Apex controller that returns the wrapper class (or a List of wrapper classes). The LWC will receive it as a standard JavaScript object.
Yes, they are the industry standard! When you receive a massive JSON payload from a third-party system, you can build a wrapper class that mirrors the JSON structure. Then, use JSON.deserialize(jsonString, MyWrapperClass.class) to instantly map the raw text into a workable Apex object.
- Constructors: Always use constructors to force developers to pass the required data when creating a wrapper instance.
- Organization: Keep small wrapper classes at the bottom of your main Apex controller file. If the wrapper is massive (like for a complex API), save it as its own independent class file.
- Security: Remember that
@AuraEnabledproperties bypass object and field-level security by default. Ensure your queries useWITH USER_MODEbefore wrapping and returning records to the frontend.