What Are Non-Primitive Data Types?
Short answer: anything that isn’t on the primitive list.
Seriously, that’s the definition. Last episode we went through the twelve primitive types. Everything else in Apex is a non-primitive, sometimes called a complex type.
That covers a lot of ground:
1. SObjects – Account, Contact, Opportunity, and every custom object you’ve built
2. Collections – Lists, Sets and Maps (all getting their own episodes shortly)
3. Objects from your own Apex classes – the MappingService we made back in episode 7
4. Objects from system-supplied classes – things like Crypto, Http, Messaging
They Have To Be Declared Somewhere
Here’s the first real difference. A primitive just exists. You can write Integer x = 1; in a completely empty org and it works.
Non-primitives have to be defined somewhere first. Either Salesforce defined it for you (standard objects, system classes), or you defined it yourself, by creating a custom object through Setup or by writing an Apex class.
The second difference is how you create one:
//A primitive. Just assign a value.
Integer distance = 1;
//A non-primitive. Note the 'new' keyword.
Contact cont = new Contact();
//Same idea with one of our own classes.
MappingService mapper = new MappingService();That new keyword is doing real work: it’s telling Apex to go build an actual instance from the blueprint. Remember the toaster analogy from episode 7? The class is the blueprint. new Contact() is an actual toaster coming off the line.
You can set fields as you go, which saves a few lines:
Contact cont = new Contact(FirstName = 'Matt', LastName = 'Gerry');
//Or afterwards, which does exactly the same thing
Contact otherCont = new Contact();
otherCont.FirstName = 'Matt';
otherCont.LastName = 'Gerry';Passed By Reference (Here’s The Gotcha)
Right. This is the one. Last episode I showed you that primitives get copied when you pass them to a method, so the original is safe.
Non-primitives are passed by reference. The method doesn’t get a copy, it gets a pointer to the exact same object sitting in memory. Which means if the method changes it, your version changes too.
Watch:
public class NonPrimitiveExample
{
public void runIt()
{
Contact cont = new Contact(FirstName = 'Matt', LastName = 'Gerry');
changeTheContact(cont);
//This prints 'Steve'. Not 'Matt'.
System.debug('The first name is ::: ' + cont.FirstName);
}
private void changeTheContact(Contact contToChange)
{
contToChange.FirstName = 'Steve';
}
}Compare that to the Integer example from last time, where the original stayed at 1. Same shape of code, completely different outcome.
This catches people out constantly, and it catches them out in the worst possible way: your code doesn’t error, it just quietly produces the wrong data. You pass a list of records into a helper method to “check” something, the helper tweaks a field, and now you’re doing DML on records you never meant to touch.
Two things to take away:
1. Know which type you’re holding. Primitive means copy. Non-primitive means the real thing.
2. If you truly need an untouched copy, clone it. SObjects have clone() built in:
Contact safeCopy = cont.clone(false, true, false, false);Those four flags control whether the Id is preserved, whether it’s a deep clone, and how timestamps and autonumbers are handled. Check the SObject class docs before you use it, because the defaults are probably not what you want.
You Can Change What They Do
The third rule flips too. Primitives are closed; non-primitives are wide open.
On a custom object you can add fields, validation rules, record types, whatever you need. On your own Apex classes you can add methods, properties, constructors, and inherit from them. There’s essentially no ceiling on what you can build.
That freedom is the whole reason object oriented programming is useful, and it’s what the rest of this series is built on.
Quick Comparison
Primitive: built into the language, passed by value (a copy), behaviour is fixed.
Non-primitive: defined by Salesforce or by you, passed by reference (the real thing), behaviour is fully extensible.
If you only remember one line from these two episodes, make it the middle one. Pass-by-value versus pass-by-reference is the difference that actually causes bugs.
What’s Next
In the next episode we’re covering variable scope: where a variable actually exists, and why the compiler sometimes claims it has never heard of something you clearly just declared.
See you next time!
Get Coding With The Force Merch!!
We now have a redbubble store setup so you can buy cool Coding With The Force merchandise! Please check it out! Every purchase goes to supporting the blog and YouTube channel.
Get Shirts Here!
Get Cups, Artwork, Coffee Cups, Bags, Masks and more here!
Check Out More Coding With The Force Stuff!
If you liked this post make sure to follow us on all our social media outlets to stay as up to date as possible with everything!
Youtube
Patreon
Github
Facebook
Twitter
Instagram
Salesforce Development Books I Recommend
Advanced Apex Programming
Salesforce Lightning Platform Enterprise Architecture
Mastering Salesforce DevOps
Good Non-SF Specific Development Books:
Clean Code
Clean Architecture