The five Map rules in Salesforce Apex start with two about uniqueness. Keys are unique, and values can repeat. Keys and values can be any data type, and non-primitive keys are matched by their contents. String keys are case-sensitive. Most quiz answers swap the third rule for "keys must be primitive types," which the Apex Developer Guide contradicts in its first paragraph on maps.
If you are a founder or CTO shipping the native Salesforce app your enterprise customers asked for, rules 4 and 5 are the ones that cost you. That app's Apex ships inside a managed package, the versioned bundle a customer installs into their own Salesforce org, and these two rules cause the bugs customers report after install. Your code runs in those subscriber orgs, which you never see. It reads record IDs pasted from spreadsheets and payload keys typed by other systems, and its triggers share static maps across before and after events. None of that shows up in your own scratch org, so it surfaces as a ticket from an enterprise customer's admin after install. Appnigma's marketplace apps team builds that package for SaaS companies, with "marketplace listing preparation, and review support."
Updated October 2026. Checked against Salesforce's Apex Developer Guide and Apex Reference Guide (Winter '27, API version 68.0). This update corrects three claims in the earlier version of this post. It said map keys must be primitive types and that sObject, list and class keys cause a compile error. It gave five as the nesting limit for collections, and it said map order is not guaranteed. It adds what each Map method returns and the sObject-key trap in triggers. It also covers the ID, casing and describe-map problems packaged code meets in customer orgs.
The five rules as the Apex docs state them
Salesforce never publishes a numbered list called "the 5 map rules," which comes from interview and certification prep. Each rule in this table is drawn from the Map Considerations on the Maps page and the Map Class reference.
| Rule | What Apex does | Risk once the package is in a customer org |
|---|---|---|
| 1. Keys are unique | A put with an existing key overwrites the entry | Two source records with one key silently become one |
| 2. Values can repeat | Many keys can point to the same value | values() can hand back the same record twice |
| 3. Any data type for keys and values | Primitives, collections, sObjects, user-defined types and built-in Apex types | Only SOQL map constructors and JSON limit key types |
| 4. Non-primitive keys match by contents | sObject keys compare field values, class keys use your equals and hashCode | A key changed after put returns null from get |
| 5. String keys are case-sensitive | 'Acme' and 'ACME' are two entries | Customer data in another case misses every lookup |
Two more behaviors complete the picture. A map key can hold null, so one null key is allowed. Iteration order is deterministic, and the docs say "You can rely on the order being the same in each subsequent execution of the same code." They still recommend reading elements by key.
Can an Apex Map key be an sObject, a list or your own class?
Yes, all three compile and run. The Maps of sObjects page says "Maps allow sObjects in their keys," and collections can be keys up to seven levels of nesting. The "primitive keys only" claim likely comes from two narrower rules that are real. A map built straight from a SOQL query needs an ID or String key, per that same page. And a Map "is serializable into JSON only if it uses one of the following data types as a key," a list of primitives plus Enum.
The catch is equality, which is rule 4. An sObject key is matched by comparing all its field values, so any field change breaks the lookup. Salesforce's sObject Map Considerations shows the case most developers hit, an insert that fills the Id field.
Account a1 = new Account(Name = 'A1');
Map<SObject, Integer> m = new Map<SObject, Integer>{
a1 => 1
};
insert a1;
// Id is now populated
Assert.isNull(m.get(a1));
// field values changed, so the key no longer matchesA class used as a key needs equals and hashCode methods, which Apex uses "to determine equality and uniqueness of keys," per Using Custom Types in Map Keys and Sets. Without them, two objects holding the same values become two keys. In practice, key by the record's Id or by a String you build from the fields that identify it. That keeps the key stable while the record changes.
Where map rules break inside a managed package
Packaged Apex meets data shapes your own test org never produces. These seven failures come straight from the documented rules, and each one shows up only after a customer installs. Your customer can't patch managed code, so every one of them waits on your next package version and a push upgrade, while the deal or renewal waits with it.
15-character IDs stored as Strings. Customers paste IDs from reports and spreadsheets, often in the 15-character form. The Apex primitive types page says that if you set an ID to a 15-character value, "Apex converts the value to its 18-character representation." A String does no such conversion. So a Map<String, Account> built from query results holds 18-character keys, and a 15-character lookup returns null. Declare the key as Id and convert inputs with Id.valueOf(), and both forms match.
Payload keys in mixed case. Field names arriving from another system, or typed into a custom setting by an admin, rarely match your casing. Rule 5 means get('accountid') misses an entry stored as 'AccountId'. Normalize every String key with toLowerCase() at the single place it enters the map, and do the same on every lookup.
Describe maps that don't follow rule 5. Describe maps, the maps Salesforce returns when your code asks which objects and fields an org has, are the exception to case-sensitive keys. The global describe page says of Schema.getGlobalDescribe() that "The sObject names are case insensitive." The field tokens page says of fields.getMap() that "All field names are case insensitive." Both return keys with your namespace prefix, the unique identifier Salesforce adds to every component in your package, such as NS1__MyObject__c, even for your own package's objects. Code that copies those keys into an ordinary Map<String, Object> loses the case-insensitivity, and code that strips the prefix to match a field name stops matching. Keep lookups against the describe map itself and include the prefix.
sObjects shared between before and after triggers. Salesforce documents this one directly. If a static map holds Trigger.new records added in a before insert trigger, the after trigger can't find them. The after-trigger records carry system fields like ID, CreatedDate and SystemModStamp, so their field values no longer match. Key the shared map by Id in after triggers, or by a field you set yourself in before triggers.
Queries inside loops. A trigger is invoked once per batch of up to 200 records, per the trigger context variables page. Code that queries once per record runs out of SOQL queries on the first data load a customer runs. Query once into a map keyed by parent Id, then read children by key inside the loop. Nesting has room for it, since map keys "can contain up to seven levels of nested collections, that is, up to eight levels overall."
Method return values read as success. put returns the old value when it overwrites, and get returns null for a missing key and for a stored null alike, per the Map Class reference. Code that ignores both merges two customers' records without a trace. Log a non-null put return and check containsKey before trusting a null.
Big maps in big orgs. Collections have no item limit, but "there is a general limit on heap size," the memory one Apex transaction can use, per the Collections page. The governor limits page sets total heap at 10 MB for synchronous Apex and 25 MB for asynchronous. A map of full sObject records that fits in a demo org can exceed that in a customer org with millions of rows. Query only the fields the logic reads, and process large volumes in batches.
What to check in map code before you release a package version
A customer can't edit the Apex inside your managed package, so a map bug in a customer org waits for your next package version. A bug caught in review costs an edit. The same bug caught by a customer costs an escalation, a new version and an upgrade for every org that installed it.
- Every String key built from external input is lowercased where it enters the map.
- Record IDs live in Id-typed keys, never Strings, so 15- and 18-character inputs match.
- No sObject or custom-class key changes after it is put in a map.
- Describe-map lookups use the describe map directly and include the namespace prefix.
- Maps shared between trigger events are keyed by Id or by a field you set.
- Tests insert 200 records with mixed-case keys and 15-character IDs.
Frequently Asked Questions
Can an Apex Map key be null?
Yes, one null key is allowed, per the Maps page. In packaged code it usually appears by accident, when an empty lookup field becomes the key and every orphan record lands under null.
Are Apex Map String keys case-sensitive?
Yes, for put, get, containsKey and remove. The describe maps from getGlobalDescribe() and fields.getMap() are the documented exception.
How many levels of nested collections can a Map hold?
Seven nested levels, eight overall, per the Apex Developer Guide. Answers that say five are out of date.
Can Appnigma build the managed package this Apex ships in?
Yes. Appnigma's marketplace apps service builds the native Salesforce app for SaaS companies, including the Apex and the managed package. It also covers "marketplace listing preparation, and review support." You "hand off platform development, packaging, and listing work while your engineers focus on the product."
About the author
Sunny Chauhan is the co-founder and CEO of Appnigma AI, which builds and maintains customer integrations and native Salesforce and HubSpot apps for B2B SaaS teams. Before Appnigma, he was on Salesforce's AppExchange team, where he worked with more than 300 SaaS partners. He has spent close to a decade on the Salesforce platform, including work at Zennify, and holds Salesforce certifications including Platform Developer II, Platform App Builder, Administrator, Data Cloud Consultant, and AI Associate.
Related Articles
- Governor Limits for Certified Managed Packages
- Salesforce Managed Package Guide, 1GP vs 2GP
- Will a Managed Package Hurt a Salesforce Org?
- What Is a Managed Package in Salesforce
- Salesforce Code Analyzer Guide
- How to Build a Salesforce Native App
Sources
- Salesforce Developers, Apex Developer Guide, Maps
- Salesforce Developers, Apex Reference Guide, Map Class
- Salesforce Developers, Apex Developer Guide, Maps of sObjects
- Salesforce Developers, Apex Developer Guide, sObject Map Considerations
- Salesforce Developers, Apex Developer Guide, Using Custom Types in Map Keys and Sets
- Salesforce Developers, Apex Developer Guide, Iterating Collections
- Salesforce Developers, Apex Developer Guide, Primitive Data Types
- Salesforce Developers, Apex Developer Guide, Collections
- Salesforce Developers, Apex Developer Guide, Lists
- Salesforce Developers, Apex Developer Guide, Trigger Context Variables
- Salesforce Developers, Apex Developer Guide, Accessing All sObjects
- Salesforce Developers, Apex Developer Guide, Using Field Tokens
- Salesforce Developers, Apex Developer Guide, Execution Governors and Limits
- Salesforce Developers, Second-Generation Packaging, Push a Package Upgrade



