Apex collections are the data structures Salesforce developers use to hold many values in one variable. There are three: a List is an ordered group of items that allows duplicates, a Set is an unordered group of unique items, and a Map stores key-value pairs with unique keys. Collections are how Apex works with records in bulk, which is essential for staying inside governor limits.
This guide was first published in 2020 as "What is the Collection in Salesforce?". It has been rewritten for 2026 and now also covers our older posts on wrapper classes and set methods. One note on the search term: "APEX collections" also refers to a feature of Oracle APEX, a different product. This page is about Salesforce Apex.
What are collections in Apex?
Apex has three collection types, List, Set and Map, and each suits a different job.
| List | Set | Map | |
|---|---|---|---|
| Holds | Ordered items of one type | Unique items of one type | Key-value pairs |
| Duplicates | Allowed | Ignored when added | Keys unique, values can repeat |
| Access by | Index, starting at 0 | Iteration or contains, no index | Key |
| Declare | List<String> names = new List<String>(); | Set<Id> ids = new Set<Id>(); | Map<Id, Account> accts = new Map<Id, Account>(); |
| Typical use | Query results, records to insert or update | IDs to query, de-duplication, membership checks | Looking up a record by ID without a loop |
Lists
A list is an ordered collection that allows duplicates and is read by index. Trailhead's Use Collections unit defines a list as an ordered group of items of the same type, where every item has a position starting at index 0. You can declare and fill a list in one line, List<String> groceries = new List<String>{'Tea','Sugar','Honey','Milk'};, or start empty and call groceries.add('Tea');. add(2, 'Milk') inserts at a position. Array notation, String[] groceries = new String[4];, creates a list with four empty slots.
Other everyday List methods, from the Apex Reference: get(index) returns the item at a position, size() returns the number of items, and sort() sorts primitives and sObjects in ascending order. Your own classes can be sorted too if they implement the Comparable interface.
A SOQL query returns a list, which is why most Apex starts with one: List<Contact> contacts = [SELECT Id, Email FROM Contact WHERE AccountId = :accId];
Sets and the Apex set methods
A set holds unique values with no order, and its methods handle union, intersection and difference. A set is an unordered collection of unique elements of one type. Adding a duplicate doesn't raise an error; it simply isn't added, as Trailhead's Define Sets and Maps unit shows. Sets have no index, so you check membership or loop over them. Trailhead's rule for choosing: if you need duplicates or order, use a list; otherwise use a set.
The methods you will use most, from the Apex Reference's Set Class:
| Method | What it does |
|---|---|
add(element) | Adds one element if it is not already present |
addAll(listOrSet) | Adds every element not already present: the union |
contains(element) | True if the element is in the set |
containsAll(listOrSet) | True if every element of the other collection is in the set |
retainAll(listOrSet) | Keeps only elements also in the other collection: the intersection |
removeAll(listOrSet) | Removes the other collection's elements: the relative complement |
isEmpty() | True if the set has no elements |
clone() | Makes a copy of the set |
retainAll and removeAll return a Boolean that tells you whether the set changed. Like map keys, String elements in a set are case-sensitive: "Abc" and "abc" are two different elements.
A common pattern is collecting IDs into a set before a query: Set<Id> accountIds = new Set<Id>(); for (Contact c : Trigger.new) { accountIds.add(c.AccountId); }. The set removes duplicates for you, so the query that follows uses each ID once.
Maps
A map stores key-value pairs: keys are unique, values can repeat. Maps are the main tool for finding a record by ID without a loop or an extra query. Use put(key, value) to add a pair and get(key) to read it; get returns null if the key isn't there. The Apex Reference's Map Class adds the methods you need most:
keySet()returns a set of the keys. It is backed by the map, so changes to one show in the other.values()returns a list of all the values.containsKey(key)checks whether a key exists. String keys are case-sensitive.putAll(otherMap)copies another map in; matching keys take the new values.
The most useful map in Salesforce development is built straight from a query, as Trailhead's Bulk Apex Triggers unit does: Map<Id, Account> accts = new Map<Id, Account>([SELECT Id, Name FROM Account WHERE Id IN :accountIds]);. Every record is keyed by its ID, so inside a loop you call accts.get(c.AccountId) instead of running a query per record.
What is a wrapper class in Salesforce?
A wrapper class is an ordinary Apex class you write to bundle different pieces of data into one object, so a collection can hold them together. The classic case is a table where users tick rows: each row needs the record and a selected flag.
public class AccountRow { @AuraEnabled public Account record; @AuraEnabled public Boolean selected; public AccountRow(Account a) { record = a; selected = false; } }
You then build a List<AccountRow> from a query and hand it to the user interface, or process the selected rows afterwards. Wrappers are also useful for combining data from several objects or an external API into one shape, and for sorting by a calculated value by implementing Comparable.
When a wrapper goes to a Lightning web component, three rules apply. The Apex method must be static, public or global and annotated with @AuraEnabled, as Trailhead's LWC and Apex unit explains, with cacheable=true only for read-only methods because cacheable methods can't run DML. And Salesforce's LWC developer guide says an Apex inner class isn't supported as a parameter or return value of a method called by a Lightning web component, so make the wrapper its own top-level class.
How collections keep you inside governor limits
Collections let you run one query and one DML statement for a whole batch of records instead of one per record. Salesforce limits each transaction to 100 synchronous SOQL queries and 150 DML statements, and triggers receive up to 200 records at a time, according to Trailhead's Bulk Apex Triggers unit. Collections are how you stay inside those limits:
- Loop over
Trigger.newand collect the IDs you need into aSet. - Run one query with
WHERE Id IN :thatSetand load the results into aMap. - Loop again, reading related data from the map, and add changed records to a
List. - Run one DML statement on the list after the loop.
Our governor limits guide has the full table, and the Apex trigger guide shows the pattern inside a trigger handler.
Common mistakes with Apex collections
Most collection bugs come from null map lookups, assumed ordering, case-sensitive keys and oversized collections.
- Calling get on a map without checking.
getreturns null for a missing key; check withcontainsKeyor handle null. - Expecting a set to keep order. Sets are unordered; use a list if order matters.
- Forgetting string keys are case-sensitive. "Acme" and "acme" are different map keys.
- Changing a keySet and being surprised. Because
keySet()is backed by the map, removing a key from it removes the entry from the map. - Holding huge collections in memory. Large lists use heap, capped at 10 MB synchronous and 25 MB asynchronous after the Winter '27 increase (all orgs from Spring '27; 6 MB and 12 MB before); query only the fields you need, and use Batch Apex for very large data sets.
Is this the same as Oracle APEX collections?
No. Oracle APEX is Oracle's low-code platform, and much of the page-one result for "apex collections" is about it. In Oracle APEX, a collection temporarily stores rows of data in session state for one user's session, managed through the PL/SQL package APEX_COLLECTION, as Oracle's APEX documentation explains. Salesforce Apex collections are in-memory data structures in Apex code that exist only for the transaction. If you are learning Salesforce development, our beginner's guide and interview questions cover collections in context.
Getting help
Techila is a Salesforce Summit Partner. If you need Apex written or reviewed, you can hire a Salesforce developer from our team, or see our Salesforce development services. Talk to us about your project.
Frequently asked questions
What are collections in Apex?
Collections are variables that hold many values. Apex has three types: a List is an ordered group of items that can contain duplicates, a Set is an unordered group of unique items, and a Map stores key-value pairs with unique keys.
When should I use a list instead of a set?
Ask two questions: do you need duplicates, and does order matter? If the answer to either is yes, use a list. If both are no, use a set; sets are typically used to check whether a value is included.
What are the most useful Set methods in Apex?
add and contains for single values; addAll for a union with another list or set; retainAll for an intersection; removeAll to subtract one collection from another; containsAll to check that every element is present; and isEmpty and clone for housekeeping.
What is a wrapper class in Salesforce?
A wrapper class is an Apex class you write to bundle different pieces of data into one object, for example an Account record plus a Boolean that records whether a row is selected in a table. A list of wrapper objects can then be shown or processed as one collection.
Can I return a wrapper class to a Lightning web component?
Yes, with care. The controller method must be static, public or global and annotated with @AuraEnabled, and the properties the component reads are marked @AuraEnabled too. Salesforce's LWC guide says an Apex inner class isn't supported as a parameter or return value, so define the wrapper as its own top-level class.
