Data Types, Strings, Dates and Operators · Lesson 4 of 41
Apex Variables and Data Types
Apex is strongly typed: every variable is declared with a type, and the compiler checks every assignment. That catches whole classes of bugs before your code ever runs – but you need to know what each type can hold, how it behaves at the edges, and how to convert between types. This lesson covers all of it in one place.
In this lesson
- Declaring variables and constants
- The primitive types and their ranges
- null, the default for everything
- Decimal vs Double, Integer division and overflow
- The Id type and type conversion
Declaring variables
Integer quantity = 3; // type name value;
String productName = 'Laptop Stand';
Boolean inStock = true;
Decimal unitPrice = 1499.99;
final Decimal GST_RATE = 0.18; // final: can be assigned only once
Integer reorderLevel; // declared, not assigned
System.debug('Reorder level: ' + reorderLevel);
Decimal total = (quantity * unitPrice * (1 + GST_RATE)).setScale(2);
System.debug(productName + ' x' + quantity + ' = ' + total);
Reorder level: null Laptop Stand x3 = 5309.96
Every variable that isn't assigned is null – including Integers and Booleans. Unlike Java, Apex has no "0 by default" for numbers. Using a null number in arithmetic throws a NullPointerException, so initialise counters explicitly: Integer count = 0;.
The primitive types
| Type | Holds | Example |
|---|---|---|
Integer | 32-bit whole number, −2,147,483,648 to 2,147,483,647 | Integer seats = 25; |
Long | 64-bit whole number | Long views = 9000000000L; |
Decimal | exact decimal number – the type of Currency and Number fields | Decimal price = 19.99; |
Double | 64-bit floating point – approximate | Double ratio = 0.333; |
Boolean | true, false – or null | Boolean isActive = true; |
String | text of any length (within heap limits) | String city = 'Pune'; |
Id | a valid 15- or 18-character Salesforce record Id | Id accId = acc.Id; |
Date, Datetime, Time | calendar dates, moments in time, times of day | Date d = Date.newInstance(2026, 11, 8); |
Blob | binary data – files, encrypted values | Blob b = Blob.valueOf('text'); |
Object | any value; cast it back to use it | Object o = 42; |
Numbers: the traps
// 1. Integer division drops the remainder
Integer a = 7, b = 2;
System.debug('7 / 2 as Integer: ' + (a / b));
System.debug('7 / 2 as Decimal: ' + (Decimal.valueOf(a) / b));
// 2. Integer overflow wraps around silently
Integer big = 2147483647;
big = big + 1;
System.debug('Integer max + 1: ' + big);
Long safe = 2147483647L + 1;
System.debug('As a Long: ' + safe);
// 3. Decimal is exact, Double is not
Decimal d1 = 0.1, d2 = 0.2;
Double f1 = 0.1, f2 = 0.2;
System.debug('Decimal 0.1 + 0.2 = ' + (d1 + d2));
System.debug('Double 0.1 + 0.2 = ' + (f1 + f2));
// 4. rounding money
Decimal amount = 1234.5678;
System.debug(amount.setScale(2) + ' | ' + amount.setScale(0, RoundingMode.DOWN) + ' | ' + amount.round(System.RoundingMode.HALF_UP));
7 / 2 as Integer: 3 7 / 2 as Decimal: 3.5 Integer max + 1: -2147483648 As a Long: 2147483648 Decimal 0.1 + 0.2 = 0.3 Double 0.1 + 0.2 = 0.30000000000000004 1234.57 | 1234 | 1235
- Use Decimal for money and anything stored in Currency, Number or Percent fields. Use Double only for scientific values where tiny errors don't matter.
- When one side of a division is Decimal, the result is Decimal. Convert before dividing, not after.
- Overflow doesn't throw – it wraps to a negative number. Use
Longfor counters that could exceed two billion (byte sizes, timestamps in milliseconds).
Booleans can be null
Boolean optedIn; // null – neither true nor false
System.debug('optedIn == true? ' + (optedIn == true));
System.debug('optedIn == false? ' + (optedIn == false));
try {
if (optedIn) { // a null Boolean in a condition throws
System.debug('never printed');
}
} catch (NullPointerException e) {
System.debug('Error: ' + e.getMessage());
}
Boolean safe = optedIn == true; // treats null as false
System.debug('safe: ' + safe);
optedIn == true? false optedIn == false? false Error: Attempt to de-reference a null object safe: false
Checkbox fields on records are never null (they default to false), but Boolean variables, wrapper properties and JSON values can be. Comparing with == true is the simple defence.
The Id type
An Id is a String that Salesforce has validated as a record Id. Its first three characters identify the object (001 = Account, 003 = Contact, 006 = Opportunity):
Account acc = new Account(Name = 'Id Demo');
insert acc;
Id accountId = acc.Id;
System.debug('18-char Id: ' + accountId);
System.debug('15-char Id: ' + String.valueOf(accountId).left(15));
System.debug('Object type: ' + accountId.getSObjectType());
Id fromShort = String.valueOf(accountId).left(15); // 15 → 18 conversion is automatic
System.debug('Same record? ' + (fromShort == accountId));
try {
Id broken = 'not-an-id';
} catch (StringException e) {
System.debug('Invalid: ' + e.getMessage());
}
18-char Id: 0015g0000000001AAA 15-char Id: 0015g0000000001 Object type: Account Same record? true Invalid: Invalid id: not-an-id
(Record Ids in this course's output are replaced with stable sample values, since every run creates new records.) Use the Id type for record Ids rather than String: invalid values fail immediately, 15- and 18-character versions compare as equal, and getSObjectType() tells you which object an Id belongs to – handy for polymorphic code.
Type conversion
// String → number
Integer qty = Integer.valueOf('42');
Decimal price = Decimal.valueOf('199.50');
System.debug(qty + 1);
System.debug(price * 2);
// number → String
String label = String.valueOf(qty) + ' units';
System.debug(label);
// String → Boolean / Date
System.debug(Boolean.valueOf('TRUE'));
System.debug(Date.valueOf('2026-11-08'));
// widening is automatic, narrowing needs a method
Long l = qty; // Integer → Long: automatic
Integer back = l.intValue(); // Long → Integer: explicit
Integer rounded = price.intValue(); // Decimal → Integer: drops decimals
System.debug(back + ' ' + rounded);
// Object needs a cast
Object anything = 'hello';
if (anything instanceof String) {
String s = (String) anything;
System.debug(s.toUpperCase());
}
try {
Integer bad = Integer.valueOf('12abc');
} catch (TypeException e) {
System.debug('TypeException: ' + e.getMessage());
}
43 399.00 42 units true 2026-11-08 00:00:00 42 199 HELLO TypeException: Invalid integer: 12abc
Practice
Try it yourself
A discount is calculated as
Integer percent = discountAmount / listPrice * 100;with Integer values 150 and 1000, and comes out as 0. Fix it.Show answer
Integer division gives 150 / 1000 = 0 before multiplying. Use Decimal:
Decimal percent = (Decimal.valueOf(discountAmount) / listPrice * 100).setScale(2);– or multiply first:discountAmount * 100 / listPrice(still Integer, 15).Why should a method parameter for a record Id be typed
Idrather thanString?Show answer
Invalid values are rejected at the boundary with a clear error, 15/18-character forms compare equal, and you can call
getSObjectType()on it.
Quick check
What is the default value of an unassigned Integer?
null.
Which type should hold currency amounts?
Decimal.
What is 7 / 2 when both are Integers?
3 – the remainder is dropped.
Summary
- Every variable is typed; unassigned variables are null, even numbers and Booleans.
- Use Decimal for money, Long for very large counts; watch Integer division and overflow.
- Use the Id type for record Ids.
- Convert with valueOf methods, and cast Object values after checking instanceof.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.