Skip links

Top 3 Apex Features in Salesforce Spring ’24 Inspired by IdeaExchange

The Spring ’24 release of Salesforce brings some exciting updates for developers, particularly with the introduction of long-awaited Apex features suggested by the community on IdeaExchange. Let’s take a closer look at each of these features and see how they help developers build more efficient and robust solutions.

Null Coalescing Operator

This feature has been on IdeaExchange since 2015 and now is finally delivered in the Spring ’24 Release.

The null coalescing operator (??) returns the left-hand argument if it’s not null, otherwise it returns the right-hand argument. This operator simplifies code by replacing verbose null checks. It’s implemented in all editions.
To use it, write a ?? b, where a is returned if not null; otherwise, b is returned. Remember, a is evaluated once, and b is only evaluated if a is null. Ensure compatibility between operand types to avoid compile errors.

I appreciate this update because the null coalescing operator can really simplify your code and make it more readable.

Here is the comparison between the previous operator and the null coalescing operator:

Integer anInteger = 6;

Integer notNullReturnValue = (anInteger == null) ? anInteger : 100;
System.debug('notNullReturnValue === ' + notNullReturnValue);

Integer notNullReturnValueWithNewOperator = anInteger ?? 100;
System.debug('notNullReturnValueWithNewOperator === ' + notNullReturnValueWithNewOperator);

====================================================================================
Success,
02:40:23.45 (46664113) |USER_DEBUG|[3]|DEBUG|notNulReturnValue === 6
02:40:23.45 (46719802) |USER_DEBUG|[7]|DEBUG|notNul(ReturnValueWithNewOperator === 6

The operator can also be used to handle SOQL results, which can return a single record or no records at all. For instance, you try to find an account by a certain Id, but a wrong Id has been passed. It leads to a QueryException. To avoid it, you can also use the new operator and handle this case.

Account defaultAccount new Account(name = 'Acme');
Account accRecord = [SELECT Id FROM Account WHERE Id = '001000000FAKEID'] ?? defaultAccount;
System.debug('accRecord === ' + accRecord);

====================================================================================
Success.
02:50:34.18 (23888410) |USER_DEBUG | [4] |DEBUG|accRecord ==== Account: (Name=Acme)

Please note that SOQL bind expressions don’t support the null coalescing operator.

class X { public String query = 'xyz';}
X x = new X();
List<Account> accounts = [SELECT Name FROM Account WHERE Name = :X??query]

Support for Randomly Generated UUID v4

Here is another great feature we have been waiting for 10+ years on IdeaExchange. 

Use the new UUID class to generate a version 4 universally unique identifier (UUID). The UUID is generated using a cryptographically strong pseudo-random number generator and is presented as 32 hexadecimal values. 

Previously, UUIDs were generated as follows: 

public static String generateUUID(){
    Blob b = Crypto.generateAesKey( size: 128);
    String h = EncodingUtil.convertToHex(b);
    String uuid = h.substring(0,8)+ '-' + h.substring(8,12) + '-' +
                  h.substring(12,16) + '-' + h.substring(16,20) + '-' + h.substring(20);
    return uuid;
}

The class provides several methods:

  • randomUUID(): Generates a random UUID that uniquely identifies an object.
  • hashCode(): Returns the hash code corresponding to the UUID instance.
  • toString(): Returns the string representation of the UUID instance.
  • fromString(string): Converts a string representation of a UUID into a UUID instance.
  • equals(obj): Compares the UUID instance with the specified object.
UUID randomUUID = UUID.randomUUID();
System.debug('#1 randomUUID === ' + randomUUID);
Integer hashcode = randomUUID.hashCode();
System.debug('#2 hashcode() === ' + randomUUID.hashCode());
String uuidStr = randomUUID.toString();
System.debug('#3 toString() === ' + uuidStr);
UUID fromStr = UUID.fromString(uuidStr);
System.debug('#4 fromString(string) === ' + fromStr);
Boolean equalsMethod = randomUUID.equals(fromStr);
System.debug('#5 equals (obj) === ' + randomUUID);

05:01:18.42 (47017327) |USER_DEBUG | [2]/DEBUG| #1 randomUUID() === 99754488-996f-4d29-945f-00f356d519a0
05:01:18.42 (47221992) |USER_DEBUG | [5]|DEBUG| #2 hashcode() === -1030745870
105:01:18.42 (47329545) |USER_DEBUG | [8]|DEBUG| #3 toString() === 99754488-996f-4029-945f-00f356d519a0
05:01:18.42 (47568203) |USER_DEBUG | [11] |DEBUG | #4 fromString(string) === 99754488-996f-4029-945f-00f356d519a0
05:01:18.42 (47768408) |USER_DEBUG | [14]|DEBUG| #5 equals(obj) === 99754488-996f-4d29-945f-00f356d519a0

Make Callouts After Rolling Back DML

This marks the third fantastic feature originating from the 2019 IdeaExchange proposal.

To revert all uncommitted DML operations, use a savepoint. Subsequently, employ the new Database.releaseSavepoint method to release savepoints explicitly before initiating a desired callout. Previously, executing callouts after creating savepoints led to a CalloutException, regardless of whether uncommitted DML existed or the modifications were rolled back to a savepoint.

Savepoint sp = Database.setSavepoint();
try {
    // Try a database operation
    insert new Account (name='Foo');
    integer bang = 1 / 0;
} catch (Exception ex) {
    Database.rollback(sp);
    Database.releaseSavepoint(sp); // Also releases any savepoints created after 'sp'
    makeACallout(); // Callout is allowed because uncommitted work is rolled back and
                    //savepoints are released
}

In this example, the savepoint is not released before executing the callout. The CalloutException indicates that you must release all active savepoints before initiating the callout.

Savepoint sp = Database.setSavepoint();
try {
    makeACallout(); 
} catch (System.CalloutException ex) {
    Assert.isTrue(ex.getMessage().contains('All active Savepoints must be released before making callouts.'));
}

In this scenario, there is pending DML when the callout is executed. The CalloutException alerts you that either the transaction must be rolled back before the callout or it needs to be committed. As a part of this update, Database.rollback(Savepoint) no longer impacts the DML row limit, though it still counts toward the DML statement limit. This modification is applicable across all API versions. Prior to Spring ’24, each rollback call incremented the DML row usage limit.

Savepoint sp = Database.setSavepoint();
insert new Account(name='Foo');
Database.releaseSavepoint(sp);
try {
    makeACallout(); 
} catch (System.CalloutException ex) {
    Assert.isTrue(ex.getMessage().contains('You have uncommitted work pending. Please commit or rollback before calling out.'));
}

Final Thoughts

In conclusion, the Spring ’24 release of Salesforce brings exciting updates for developers, including long-awaited Apex features from IdeaExchange suggestions. These enhancements aim to simplify code, add new functionality, and improve the developer experience. As developers explore and utilize these new capabilities, they contribute to the ongoing innovation and success of the Salesforce platform.