Blog Posts

Beginner’s Guide to Data Types in Salesforce Apex for Salesforce Admins & Developers

Data types represent the foundation of every single Apex application, bringing shape to different actions and processes in the Salesforce ecosystem. Even though Apex has a lot of roots in Java, it also has an assortment of platform-specific elements, such as SObjects, that make it different and unique when compared with other programming languages.

Gaining a grip on the data types that Apex is composed of is not just about writing code in itself – it is also about creating solutions that can offer effortless scaling and impressive performance when it is needed the most.

What are the Different Salesforce Data Types?

As mentioned before, data types are one of the most basic pieces of knowledge about Apex applications, and every programmer should have a grasp of them. Before we can dive into specific implementations of data types, it would be wise to offer a short but concise reminder of the different data types that Salesforce code operates with.

Understanding Primitive Data Types in Salesforce

Primitive data types are the most common material for creating Apex scripts or programs, including the following options:

  • Integer – data type most suitable for simple calculations, holds whole numbers without any decimals.
  • Decimal – works with highly specific numbers, often used in financial calculations and other actions that require a high degree of accuracy.
  • Double – capable of storing decimals with faster processing than Decimals but lower precision.
  • Boolean – can hold basic true/false values for logical operations or conditional statements.
  • String – provides text data, ranging from lengthy paragraphs to single characters and everything in between.
  • Long – operates with larger whole numbers in situations where Integer alone is not enough.
  • Date – provides date values without time information.
  • DateTime – a combination of time and date information.
  • Time – offers time-of-day values and nothing else.

Working with Strings in Salesforce

String manipulation is an exceedingly important element of data processing with Apex; it provides a powerful text-handling tool with a variety of options. Aside from basic storage capabilities, Strings in Salesforce can also simplify common text operations in many ways, be it basic formatting or complex pattern matching. Additionally, the immutable nature of Strings guarantees data integrity while also offering many different operations for manipulating text messages and inputs.

For example, here are a few basic text manipulations with String – upper case text, a limited number of symbols shown, a Boolean true/false statement, and more:

String text = ‘Hello Salesforce’;

String upper = text.toUpperCase();        // HELLO SALESFORCE

String sub = text.substring(0, 5);        // Hello

Boolean contains = text.contains(‘Sales’); // true

// String formatting

String name = ‘John’;

String formatted = String.format(‘Hello {0}’, new List<String>{name});

We would like to mention that all bold text in this section and further into the article is not a part of the code itself but rather an explanation of sorts for the associated action.

Understanding Collection Data Types in Salesforce

Collection types are an extension of the way Apex commands can handle large data groups without losing efficiency. There are three primary data types that serve distinct purposes in Apex:

Lists – Ordered collections of information that also allow duplicate values, for example:

List<String> names = new List<String>();

names.add(‘Kyle’);

names.add(‘Richard’);

names.add(‘Kyle’); // Allowed in Lists

Sets – Unordered collections of unique elements that do not allow duplicates, for example:

Set<String> uniqueNames = new Set<String>();

uniqueNames.add(‘Kyle’);

uniqueNames.add(‘Richard’);

uniqueNames.add(‘Kyle’); // Ignored, Set remains unchanged

Maps – Collections of key-value pairs, extremely useful in specific situations, for example:

Map<Id, Account> accountMap = new Map<Id, Account>();

accountMap.put(account.Id, account);

Account retrieved = accountMap.get(account.Id);

Exploring SObject Data Types

SObjects are the proverbial bridge between Salesforce’s database layer and Apex code of the user. It is a collection of specialized data types that manage to encapsulate the complexities of Salesforce records in order to offer a seamless data manipulation interface. A good understanding of SObjects’ importance is essential due to their value in most Salesforce operations – complex data transformations,  simple record updates, and so on.

Here is a standard example of an SObject:

Account newAccount = new Account();

newAccount.Name = ‘Test Account’;

And this is one of the most basic examples of how SObjects can be utilized in other operations:

SObject genericRecord = new Account();

How to Use Custom Object Data Types

In addition to everything mentioned before, Salesforce also allows for the creation of custom objects that extend the platform’s data model capabilities. The ability to create specialized data types to match a company’s business requirements is extremely useful since custom data types behave in a similar manner to standard SObjects but have enough flexibility to define their own relationships and fields. Custom object data types provide many possibilities for creating highly personalized and sophisticated solutions with enough programming experience.

For example, a basic custom object in Salesforce may look like this:

Project__c newProject = new Project__c();

newProject.Name = ‘New Project’;

newProject.Status__c = ‘Active’;

Date and DateTime in Salesforce

The biggest reason why we mention Date and DateTime separately from the rest is due to the importance of temporal data handling in Salesforce environments. A lot of the business logic and data accuracy processes depend heavily on timing and time-based operations, making it an invaluable part of scheduling, reporting, and compliance tracking processes, among others. They can even handle timezone conversions in an automatic manner, making them even more valuable for companies with global applications.

For example, we can offer a number of basic operations with Date and DateTime here:

// Working with Dates

Date today = Date.today();

Date futureDate = today.addDays(30);

Date firstOfMonth = today.toStartOfMonth();

// Working with DateTimes

DateTime now = DateTime.now();

DateTime utcTime = DateTime.newInstanceGmt(2025, 1, 1);

// Converting between Date and DateTime

DateTime dateTimeFromDate = DateTime.newInstance(today.year(), today.month(), today.day());

How Do Primitive Data Types Work in Salesforce Apex?

Primitive data types might seem straightforward at first (especially if their name is taken at face value), but the proper implementation of such data can have a significant positive effect on an application’s performance and reliability, making it an important part of the development process. The plan for this section is to go over different primitive data types to show how they can be used.

What is a Boolean Variable?

A Boolean variable in Apex is a step above serving as a simple true/false storage. It is now an essential part of decision-making logic and control flow, being able to optimize memory usage by occupying only a single bit (a perfect data type for state tracking and flags).

For example, here is a simple code fragment that takes advantage of Boolean’s two-dimensional nature to simplify a conditional statement:

Boolean isActive = true;

Boolean hasPermission = user.Profile.PermissionSet.contains(‘Admin’);

// Useful in conditional statements

if (isActive && hasPermission) {

    // Execute privileged code

}

There is also a rather common pitfall that should be avoided when using Boolean variables in Apex, presented in a simple manner below:

Boolean result = isActive == true; // Redundant comparison

Boolean betterResult = isActive;   // Preferred approach

Using Decimal and Integer in Apex

The choice between numeric data types in Apex is also important, considering how many calculations may impact overall business logic in one way or another. For example, Integers offer faster processing but only work with whole numbers. Alternatively, Decimals are slower but can work with decimal point arithmetic with higher accuracy.

For example, we can offer two simple calculations that showcase how Integer differs from Decimal:

// Integer operations

Integer count = 7;

Integer result = count * 2;       // Simple multiplication

Integer division = 14/5;          // Results in 2 (truncated)

// Decimal operations

Decimal price = 24.99;

Decimal tax = 0.11;

Decimal total = price * (1 + tax);

total = total.setScale(2);        // Rounds to 2 decimal places: 27.74

What are the Characteristics of 64-bit and 32-bit Numbers?

Precision on regards to numerical values in Apex varies significantly based on the chosen data type. Knowledge of distinct differences between data types in this context is essential information for maintaining data accuracy in calculations.

For example, here are the differences between 32-bit and 64-bit numbers, as well as a comparison between a 64-bit (Double) type and a Decimal type to showcase the potential accuracy loss:

// 32-bit Integer (-2,147,483,648 to 2,147,483,647)

Integer regularNum = 2147483647;

Integer overflow = regularNum + 1;  // Causes overflow

// 64-bit Long

Long bigNumber = 9223372036854775807L;

Long withSuffix = 123L;            // ‘L’ suffix denotes Long

// Double (64-bit) vs Decimal comparison

Double imprecise = 0.1 + 0.2;      // Might result in 0.30000000000000004

Decimal precise = 0.1 + 0.2;       // Exactly 0.3

Working with String Variables and Methods

String variable manipulation is a powerful method of text processing that has a lot of potential when it comes to performance optimization and general efficiency. We can show a few examples of such actions and commands below:

String text = ‘Salesforce Development’;

// String methods

String lower = text.toLowerCase();

List<String> parts = text.split(‘ ‘);

// Performance-conscious string building

String[] parts = new String[]{‘Hello’, ‘World’};

String joined = String.join(parts, ‘ ‘);  // Better than concatenation in loops

// String template patterns

String template = ‘Hello {0}, welcome to {1}’;

String message = String.format(template, new String[]{‘John’, ‘Salesforce’});

Type Conversion between Primitive Data Types

It is also possible to convert one primitive data type into another using Apex code, although there are many implicit and explicit rules that have to be kept in mind when attempting to do so without running into runtime errors and other issues:

// Numeric conversions

Integer intVal = 100;

Double doubleVal = intVal;                    // Implicit conversion

Integer backToInt = doubleVal.intValue();     // Explicit conversion

// String conversions

String strNum = ‘123’;

Integer parsedInt = Integer.valueOf(strNum);   // String to Integer

String backToString = String.valueOf(parsedInt); // Integer to String

// Date/Time conversions

String dateStr = ‘2024-01-01’;

Date parsed = Date.valueOf(dateStr);

DateTime dt = DateTime.newInstance(parsed.year(), parsed.month(), parsed.day());

// Common conversion pitfalls to avoid

try {

    Integer badParse = Integer.valueOf(‘not a number’); // Throws exception

} catch (Exception e) {

    System.debug(‘Handle conversion errors gracefully’);

}

What are SObject and SObject Data Types in Apex?

SObjects are the built-in way for Salesforce to handle database records in the code form. They are somewhat similar to digital containers in nature, holding all the necessary information about a particular record – be it a sales opportunity, a customer account, or any other data type.

How to Define an SObject Variable?

When working on SObjects, there are two primary approaches to record management. There is a standard, generic approach, and there is also a way to specify what kind of record you are working with. The former is more flexible when it comes to handling different data types in the same code, while the latter is safer and contributes to better code completion.

A lot of developers prefer the specific approach due to the combination of safety and clarity, but there are also many situations where the immense value of the generic approach outweighs its lack of accuracy. You can find standard examples of both approaches below:

// Generic approach

SObject genericRecord = new Account();

Account typedAccount = (Account)genericRecord;

// Specific approach

Account newAccount = new Account();

newAccount.Name = ‘Acme Corp’;

Manipulating Data with SObjects

One of the most notable use cases for SObjects is data manipulation, and there are two major approaches to this task – direct field access and dynamic field access. There are many differences between the two, but the most significant difference is the fact that direct access is straightforward, and dynamic access is flexible.

The fact that SObjects can handle relationships with objects in such an easy fashion is a notable advantage of the environment. The creation of complex data sequences can be drastically simplified when there is an option to navigate from an opportunity to a related product, from a contact to an account, or practically any other combination.

Here are two basic examples of direct and dynamic field access:

// Direct field access

Account acc = new Account();

acc.Name = ‘Test Account’;

// Dynamic field access

acc.put(‘Industry’, ‘Technology’);

// Relationship navigation

Contact cont = [SELECT Account.Name 

               FROM Contact 

               WHERE Id = :contactId];

Custom fields in SObjects

Custom fields in SObjects have a somewhat obvious purpose – to store information that cannot be defined by one of the existing field types. All custom fields in SObjects have a very specific naming convention – they always end with a “__c.” This kind of naming convention is another step toward code simplification, making custom fields easier to distinguish from standard Salesforce fields. Custom fields can be powerful and flexible, but they have a much higher baseline in terms of programming knowledge to operate properly.

Salesforce has its own dedicated tools for discovering and validating custom fields at runtime – an essential feature when there is a necessity to build a flexible application that would have to adapt to different configurations of organizations. Here is how a custom field in SObjects might look like:

Account acc = new Account();

acc.Custom_Field__c = ‘Custom Value’;

// Runtime field validation

if (acc.isSet(‘Custom_Field__c’)) {

    // Field has a value

}

How to Use Enum in Salesforce Apex?

Enum is an abstract data type in Salesforce Apex that offers a streamlined way of creating a predefined set of values. It is often used as the means of organizing code while attempting to make it less prone to errors. Even though Apex takes a lot from other programming languages, it also has a number of platform-specific characteristics that can be particularly useful in Salesforce environments.

Understanding the Set of Named Constants

The best way to approach enums is a menu of fixed choices to use in code. Enums are predefined and can be validated by any code editor, making them a lot more reliable than regular strings or numbers of options that can be typed incorrectly and are challenging to resolve. 

Every single value in an enum is a constant that is not changeable in any way once it is defined. It is a great option for representing fixed states or categories in an application. Here is a basic example of how enum can be used to present several options to choose from in a structured manner:

public enum Season {

    WINTER,

    SPRING,

    SUMMER,

    FALL

}

// Using the enum

Season currentSeason = Season.SUMMER;

When to Use Enums in Apex Programming

Enums are excellent in situations where there is a need for a fixed set of options that are more or less set in stone. There are three primary advantages that enums can offer to Apex code:

  • Type safety is assured by the fact that it is impossible to use the invalid value if it was not defined in the enum beforehand – the code would just refuse to compile in the first place.
  • Maintainable code is possible due to the fact that enum values are defined in one place, simplifying both referencing and updating sequences.
  • Better code readability is a simple advantage of the fact that enum is not presented as a string constant or a numeric code, making it a lot easier to understand.

Best Practices for Enum Implementation

There are certain patterns that can be followed when working with enums in order to keep the code readable and easily maintainable. In order to distinguish enums from regular variables, it is recommended to keep them all in UPPERCASE and singular when possible (for example, “Statuses” is not recommended, with “Status” being the better option).

Enums also cannot be extended once defined; it is not the most flexible option available. However, they work great with switch statements, validation rules, and picklist fields – even if we’d recommend ensuring the correct value alignment first.

Below, you’ll find an example of a simple status order code fragment that uses enum as the means of performing specific actions based on the current value of the enum:

public enum OrderStatus {

    NEW,

    IN_PROGRESS,

    SHIPPED,

    DELIVERED,

    CANCELLED

}

public class OrderProcessor {

    public void processOrder(Order__c order, OrderStatus newStatus) {

        switch on newStatus {

            when NEW {

                initializeOrder(order);

            }

            when IN_PROGRESS {

                startProcessing(order);

            }

            when else {

                handleOtherStatuses(order, newStatus);

            }

        }

    }

}

How to Properly Initialize Apex Variables?

Variable initialization in Salesforce Apex is not just the value assignment process. Variables have to be set up in a way that can ensure the reliability and efficiency of code execution while avoiding pitfalls and issues that might cause runtime errors.

Common Mistakes in Apex Variables Initialization

Incorrect assumptions about default values and variable scope are the cause of many initialization mistakes. Apex has a set of very specific rules on how variables should behave in different situations, and learning these rules might prevent a lot of common issues from appearing in the first place.

For example, class-level variables do not behave the same way as method-level variables, and there should also be a lot of attention paid to collection initialization as a process. The context for different commands can be the game-changer, throwing an error in one situation and working fine in another, making the knowledge about all these differences extremely important to an average Apex programmer:

public class InitializationExample {

    // Class-level variables get default values

    private String classLevelString;        // null

    private Integer classLevelInteger;      // 0

    public void demonstrateMistakes() {

        // Method-level variables must be initialized

        String localString;

        // System.debug(localString);  // Error: Variable must be initialized

        // Common collection mistake

        List<Account> accounts;

        // accounts.add(new Account());  // NullPointerException

    }

}

Initializing Collection Variables for Complex Objects

Initialization patterns for complex objects benefit greatly from a structured initialization pattern, especially when it comes to nested structures. The initialization process in such situations can also use factory methods or builder patterns to simplify itself to a certain degree while also making it less prone to errors. 

Here’s an example of a complex initialization sequence code:

public class ComplexInitialization {

    public static Account createFullAccount() {

        Account acc = new Account(Name = ‘New Account’);

        // Related contacts

        acc.Contacts = new List<Contact>{

            new Contact(FirstName = ‘John’, LastName = ‘Doe’),

            new Contact(FirstName = ‘Jane’, LastName = ‘Smith’)

        };

        return acc;

    }

    // Builder pattern example

    public class AccountBuilder {

        private Account acc;

        public AccountBuilder() {

            this.acc = new Account();

        }

        public AccountBuilder withName(String name) {

            acc.Name = name;

            return this;

        }

        public Account build() {

            return acc;

        }

    }

}

Best Practices for Using Specific Data Types

Every single Apex data type has its own initialization considerations. These nuances can help create more robust code while avoiding unexpected behavior or errors.

Primitive types, for example, necessitate explicit initialization in order to make the code’s intentions clear. 

Alternatively, collections should always be initialized before being used, even if they are empty at the moment, and SObjects require additional considerations for whether there is a need to initialize optional fields or not.

public class BestPracticesDemo {

    // Primitive types

    private String description = ”;        // Better than null for strings

    private Integer counter = 0;            // Explicit starting point

    private Boolean isActive = false;       // Clear initial state

    // Collections

    private List<String> items = new List<String>();

    private Set<Id> uniqueIds = new Set<Id>();

    private Map<String, Object> dataMap = new Map<String, Object>();

    // SObjects

    private Account defaultAccount = new Account(

        Name = ‘Default Account’,

        Type = ‘Prospect’

    );

}

How to Handle Binary Data in Salesforce?

Binary data handling in Salesforce necessitates special attention due to the fact that it works with raw data, attachments, and files in a different way compared with traditional text or number processing rules. These differences are necessary in order to achieve success in managing binary content in a Salesforce org.

What is Collection of Binary Data?

Binary data in Salesforce has many forms, such as image files, encrypted data, document attachments, etc. The platform has its own dedicated tools and data types to handle such information in a more efficient manner (mostly revolving around the data type called Blob). 

Blob can be seen as a container of sorts that can store any variation of binary content – an image, a PDF, a record, etc. Salesforce’s handling of binary data attempts to balance accessibility and storage efficiency. The process itself can be dumbed down to the encoding and decoding processes for extremely long streams of bytes (zeroes and ones).

A simple example of a blob data encoding/decoding command in Apex can be found below:

// Working with Blob data

String myString = ‘Sample Text’;

Blob binaryData = Blob.valueOf(myString);

String decoded = binaryData.toString();

// Base64 encoding/decoding

String base64String = EncodingUtil.base64Encode(binaryData);

Blob decodedBlob = EncodingUtil.base64Decode(base64String);

Storing Attachments in Salesforce

There are two different approaches to storing binary data as attachments in Salesforce:

  • Modern approach relies on ContentDocument and ContentVersion objects.
  • Traditional approach uses the Attachment object.

Each of these approaches has its own use cases and advantages, and knowing where to use a specific approach is an important element of efficient file management in Salesforce. 

The fact that files in Salesforce can be associated with multiple records simplifies this topic to a certain degree. Such flexibility also comes with a number of considerations toward access controls and storage limits, as well. 

Here is how both traditional and modern approaches to storing attachments work in Apex:

// Traditional attachment approach

Attachment att = new Attachment();

att.Name = ‘sample.txt’;

att.Body = Blob.valueOf(‘Sample Content’);

att.ParentId = recordId;  // ID of the parent record

insert att;

// Creating a ContentVersion (modern approach)

ContentVersion cv = new ContentVersion();

cv.Title = ‘Sample Document’;

cv.PathOnClient = ‘sample.pdf’;

cv.VersionData = Blob.valueOf(‘Sample Content’);

cv.IsMajorVersion = true;

insert cv;

Binary data is where organizations also often encounter the issue of data storage, considering how specific types of information might take a substantial amount of storage space. This not only puts a lot of pressure on Salesforce’s internal storage management but also complicates retrieval in some cases. 

Luckily, there are many third-party solutions like GRAX that can address these concerns. GRAX can offer specialized data handling protocols that optimize storage efficiency and maintain native Salesforce functionality at the same time, making binary data accessible without consuming Salesforce storage space.

GRAX can also provide substantial advantages in the field of binary data security, with its archival processes being fine-tuned for long-term storage with version control, easy access to archived information, and many storage optimization capabilities where possible. This kind of approach can help keep up the performance of a Salesforce org while also making sure that all historical binary data remains accessible at a moment’s notice.

How to optimize storage efficiency?

Ensure the accessibility of all historical binary data with GRAX.

Watch Demo

Conclusion

Learning to work with the data types that Salesforce Apex deals with is the difference between a basic code and an efficient and reliable code. Each type serves a specific purpose in the broad Salesforce ecosystem – from SObjects and primitive types to binary data management and many others. Sufficient knowledge about these data types is not just a requirement – it is a fundamental skill that can significantly impact all aspects of the Salesforce development process.

Proper data type usage helps improve application performance, enhances the code’s ability to be improved upon and maintained, and even helps prevent common runtime errors. The relationships between data types are just as important – with conversion between types being an invaluable skill, SObject collections, and many other examples of how Salesforce data is interconnected and dependent on each other, even at the code level.

As Salesforce applications grow in complexity and scope, it is important to remember that the correct choice of a data type is not just about information storage – it is also about creating a foundation for scalable and maintainable solutions with high performance. It does not matter whether you work on batch processes, triggers, or integration code – the correct understanding of data types as a topic is always going to be relevant to your success as a Salesforce developer.

Frequently Asked Questions

What is the best way to handle decimal precision in financial calculations?

Decimal type is heavily recommended in most financial calculations instead of Double due to its overwhelming precision – a crucial factor for monetary values. Consistent decimal placements can be set up using the setScale() method and different decimal operations might require varying precision levels, but none of these nuances are particularly deal-breaking.

What is the best option between Date and DateTime data types?

Date is at its best when there is only a need to track calendar dates – due dates, birthdates, etc. DateTime has a different use case where there is a need to track both parameters on a regular basis. There is also the fact that DateTime is the only option that provides timezone information.

Is there any major difference between SObject and Custom Object?

Standard SObject is a built-in Salesforce object – Account, Contact, etc. Alternatively, Custom Objects are highly specific for each organization and always end with a “__c” as their name. Both options can be considered SObjects at their core, but the flexibility to model unique business data is only available to Custom Objects.

See all
GRAX

Join the best
with GRAX Enterprise.

Be among the smartest companies in the world.