Techila
Techila/Insights/Apex Collections in Salesforce: Lists, Sets, Maps and Wrapper Classes

Salesforce · 8 min read

Apex Collections in Salesforce: Lists, Sets, Maps and Wrapper Classes

Category

Salesforce

Read time

8 min read

Author

Techila Salesforce Practice

Published

25 Mar 2020

Last updated

10 Nov 2026

Apex collections are the three data structures Salesforce developers use to hold many values at once: lists (ordered, duplicates allowed), sets (unique, unordered) and maps (key-value pairs). Here is how each works, the key set and map methods, wrapper classes, and how collections keep code inside governor limits.

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.

ListSetMap
HoldsOrdered items of one typeUnique items of one typeKey-value pairs
DuplicatesAllowedIgnored when addedKeys unique, values can repeat
Access byIndex, starting at 0Iteration or contains, no indexKey
DeclareList<String> names = new List<String>();Set<Id> ids = new Set<Id>();Map<Id, Account> accts = new Map<Id, Account>();
Typical useQuery results, records to insert or updateIDs to query, de-duplication, membership checksLooking 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:

MethodWhat 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:

  1. Loop over Trigger.new and collect the IDs you need into a Set.
  2. Run one query with WHERE Id IN :thatSet and load the results into a Map.
  3. Loop again, reading related data from the map, and add changed records to a List.
  4. 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. get returns null for a missing key; check with containsKey or 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.

More perspectives.

Let's now look at how to build
the next 10 years of
your customer platform.

See case studies