Data Types, Strings, Dates and Operators · Lesson 7 of 41
Apex Operators: Arithmetic, Comparison, ?. and ??
Operators are the small symbols that do the work in every expression. Most behave exactly as in Java – but Apex has a few of its own: string comparison ignores case, === compares references, and two newer operators, ?. and ??, remove most of the null checks that used to clutter Apex code.
In this lesson
- Arithmetic and assignment operators
- Comparison: == vs ===
- Logical operators and short-circuiting
- Ternary, instanceof
- Safe navigation (?.) and null coalescing (??)
Arithmetic and assignment
Integer stock = 100;
stock -= 15; // stock = stock - 15
stock *= 2;
Integer boxes = stock / 12;
Integer loose = Math.mod(stock, 12); // Apex has no % operator – use Math.mod
System.debug('stock ' + stock + ' → ' + boxes + ' boxes + ' + loose + ' loose');
Integer i = 5;
Integer post = i++; // use, then add
Integer pre = ++i; // add, then use
System.debug('post ' + post + ', pre ' + pre + ', i ' + i);
String code = 'ORD-' + 2026 + '-' + 7; // + with a String concatenates
System.debug(code);
System.debug(1 + 2 + ' items'); // left to right: 3 + ' items'
System.debug('items: ' + 1 + 2); // 'items: 1' + 2
stock 170 → 14 boxes + 2 loose post 5, pre 7, i 7 ORD-2026-7 3 items items: 12
Apex has no % operator – use Math.mod(a, b). And watch expressions that mix numbers and strings: evaluation runs left to right, so once a String appears, + means "join".
Comparison: == vs ===
System.debug('10 > 9: ' + (10 > 9) + ', 10 != 9: ' + (10 != 9));
System.debug('strings, ==: ' + ('Closed Won' == 'closed won')); // case-insensitive
System.debug('strings, <: ' + ('apple' < 'Banana')); // case-insensitive too
Account a1 = new Account(Name = 'Acme');
Account a2 = new Account(Name = 'Acme');
Account a3 = a1;
System.debug('a1 == a2: ' + (a1 == a2)); // sObjects: compares field values
System.debug('a1 === a2: ' + (a1 === a2)); // exact equality: same object in memory?
System.debug('a1 === a3: ' + (a1 === a3));
Integer missing = null;
System.debug('null == null: ' + (missing == null));
10 > 9: true, 10 != 9: true strings, ==: true strings, <: true a1 == a2: true a1 === a2: false a1 === a3: true null == null: true
==compares values: for sObjects and collections it compares the contents; for Strings it ignores case.===and!==check whether two references point to the same object.- For custom classes,
==compares references unless the class definesequals()andhashCode().
Logical operators short-circuit
Opportunity opp = null;
// && stops at the first false, so opp.Amount is never read when opp is null
if (opp != null && opp.Amount > 100000) {
System.debug('big deal');
} else {
System.debug('no opportunity, no error');
}
Boolean isManager = false, isOwner = true;
System.debug('can edit: ' + (isManager || isOwner));
System.debug('read only: ' + !(isManager || isOwner));
no opportunity, no error can edit: true read only: false
Ternary and instanceof
Decimal amount = 250000;
String tier = amount >= 1000000 ? 'Enterprise' : amount >= 100000 ? 'Mid-market' : 'SMB';
System.debug(tier);
Object value = Date.newInstance(2026, 11, 8);
System.debug('Date? ' + (value instanceof Date) + ', String? ' + (value instanceof String));
SObject rec = new Contact(LastName = 'Rao');
if (rec instanceof Contact) {
Contact c = (Contact) rec;
System.debug('Contact: ' + c.LastName);
}
Mid-market Date? true, String? false Contact: Rao
Safe navigation ?. and null coalescing ??
Records from queries often have empty lookups and blank fields. The safe navigation operator ?. stops and returns null when the left side is null, instead of throwing. The null coalescing operator ?? supplies a fallback when a value is null:
insert new Account(Name = 'Orphan Contact Co');
Contact c = new Contact(LastName = 'Iyer'); // no Account
insert c;
c = [SELECT LastName, Account.Name, Account.Owner.Name, Email FROM Contact WHERE Id = :c.Id];
// old way: nested null checks
String oldWay = (c.Account != null && c.Account.Name != null) ? c.Account.Name : 'No account';
System.debug('old way: ' + oldWay);
// new way
System.debug('?. : ' + c.Account?.Name);
System.debug('?? : ' + (c.Account?.Name ?? 'No account'));
System.debug('email: ' + (c.Email ?? 'no email on file'));
// ?? with a single-row query: no "List has no rows" exception
Account fallback = new Account(Name = 'Default account');
Account found = [SELECT Name FROM Account WHERE Name = 'Does Not Exist' LIMIT 1] ?? fallback;
System.debug('found: ' + found.Name);
// ?. does NOT protect list indexing – an empty list still throws
List<Account> none = [SELECT Name FROM Account WHERE Name = 'Does Not Exist'];
try {
System.debug(none[0]?.Name);
} catch (ListException e) {
System.debug('ListException: ' + e.getMessage());
}
old way: No account ?. : null ?? : No account email: no email on file found: Default account ListException: List index out of bounds: 0
Single-row queries: use ??, not ?.
Assigning a query to one record – Account a = [SELECT … LIMIT 1]; – throws "List has no rows for assignment to SObject" when nothing matches. Adding ?? fallback to the query returns the fallback instead (null is allowed too). ?. can't help here, and it doesn't make list[0] safe either. The ?? operator needs API version 60.0 or later.
Operator precedence, briefly
From highest to lowest: grouping ( ), unary ! ++ --, * /, + -, comparisons < > <= >=, equality == !=, &&, ||, ??, ternary ?:, assignment. When in doubt, add parentheses – they cost nothing and help the next reader.
Practice
Try it yourself
Rewrite with ?. and ??:
String region = (opp.Account != null && opp.Account.BillingState != null) ? opp.Account.BillingState : 'Unknown';Show answer
String region = opp.Account?.BillingState ?? 'Unknown';What does
System.debug('Total: ' + 10 + 5);print, and how do you print 15?Show answer
"Total: 105" – the String is joined with 10, then with 5. Use parentheses:
'Total: ' + (10 + 5).
Quick check
How do you get a remainder in Apex?
Math.mod(a, b) – there is no % operator.
What's the difference between == and ===?
== compares values (case-insensitive for Strings); === checks for the same object reference.
What does a?.b return if a is null?
null, without throwing.
Summary
- Use Math.mod for remainders; mind left-to-right String concatenation.
- == compares values (ignoring case for Strings); === compares references.
- && and || short-circuit, so put null checks first.
- ?. and ?? replace most manual null checks.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.