Salesforce Developers Blog

A Deep Dive into Winter ’24 Apex Enhancements

Avatar for Mohith ShrivastavaMohith Shrivastava
The Winter ’24 release of the Salesforce Platform has brought significant improvements for Apex developers.
A Deep Dive into Winter ’24 Apex Enhancements
November 14, 2023
Listen to this article
0:00 / 0:00

The Winter ’24 release of the Salesforce Platform, generally available as of Oct 16th, added some important quality-of-life improvements for Apex developers. In this blog post, we’ll walk you through the latest Apex updates introduced in the Winter ’24 release and explore these updates with practical code examples to see how they simplify writing business logic. You can see some of them in action in the apex-recipes sample app.

DataWeave in Apex

Your application may often have the need to read, parse, and transform data between different data formats, such as CSV, JSON, XML, and Apex objects. To transform between formats, you need code that requires knowing the specifics of each format.

DataWeave in Apex simplifies data transformations between formats. It integrates MuleSoft’s DataWeave library into the Apex runtime, enabling the execution of DataWeave scripts. These scripts use MuleSoft’s DataWeave language to easily transform data without you having to write complex code to serialize and parse data. You can create DataWeave scripts as metadata and call them directly from Apex code. This feature streamlines data transformations in Apex. There is also a new UI, which is part of the Setup screen, where you can view DataWeave scripts in Salesforce.

Note – You don’t have to be a MuleSoft customer or have any specific Salesforce license to use DataWeave in Apex.

Let’s take a look at a code example from the Apex recipe that shows how to use DataWeave in Apex to convert a CSV string to Contact object records in Salesforce.

The DataWeave script csvToContacts.dwl for converting CSV string to Contacts in Salesforce is shown below.

1%dw 2.0
2input records application/csv
3output application/apex
4---
5records map(record) -> {
6 FirstName: record.first_name,
7 LastName: record.last_name,
8 Email: record.email
9} as Object {class: "Contact"}

The above script is deployed as metadata. The platform then automatically creates an Apex class DataWeaveScriptResource.csvToContacts with a method execute to transform a CSV string into Contact records.

The following is an example code snippet from apex-recipes using DataWeaveScriptResource.csvToContacts.

1public with sharing class TransformSobjectsInDataWeave {
2     // The class uses the `/dw/csvToContacts.dwl` script to convert CSV
3     // to list of contact records. Notice that in the `csvToContacts.dwl` the
4     // output MIME type is `application/apex`
5     public static List convertCsvToContacts(String inputCsv) {
6        DataWeave.Script script = new DataWeaveScriptResource.csvToContacts();
7        DataWeave.Result dwresult = script.execute(
8            new Map<String, Object>{ 'records' => inputCsv }
9        );
10        List results = (List) dwresult.getValue();
11        return results;
12    }
13}

You can check if the code is functional or not using the code snippets below.

1// CSV data for Contacts
2String inputCsv = 'first_name,last_name,email\nCodey,"The Bear",codey@salesforce.com';
3List results = convertCsvToContacts(inputCsv);

For complete code, check the code in the TransformSobjectsInDataWeave.cls and TransformSobjectsInDataWeave_Tests.cls files in apex-recipes. As you can see from the recipe, you can simply focus on your business logic rather than writing your own parsers.

We have added byte-size recipes to make it easier for you to learn DataWeave in Apex. Be sure to check the folder DataWeaveInApex Recipes, which includes basic examples of using static and dynamic syntax, CSV to JSON conversion, and recipes that show how to transform CSV/JSON into Apex objects. For more examples, see the DataWeaveInApex repo.

To learn more about how to write DataWeave scripts, we recommend using the DataWeave playground and online tutorial. Also, use the DataWeave 2.0 (Beta) VisualStudio code extension to build and test your DataWeave scripts. This extension is also available in Code Builder.

Comparator interface and Collator class for sorting

Prior to the Winter ’24 release, if you had to sort a list of non-primitive data types, you had to write a wrapper class implementing a Comparable interface. With the new Comparator interface, you can now implement sort orders without wrapper classes. List.sort() now accepts a class implemented using a Comparator interface (see docs).

The code for the AccountShippingCountryComparator implementing a custom sort order from apex-recipes is shown below.

1/**
2* @description An example implementation of the Comparator Interface
3* In this example we show how to sort all the accounts by their country names in alphabetical order
4**/
5
6public with sharing class AccountShippingCountryComparator implements Comparator<Account> {
7/**
8* @description This exception class is for throwing a custom exception
9*/
10public class ASCComparatorException extends Exception {
11}
12
13public SortOrder order {
14  get;
15  set {
16    order = value;
17   }
18}
19
20// Asc and Desc are reserved keywords in Apex!
21public enum SortOrder {
22   ASCENDING,
23   DESCENDING
24}
25
26/**
27* @description No param constructor. Assigns sort order as ascending by default
28*/
29public AccountShippingCountryComparator() {
30  order = SortOrder.ASCENDING; // use ascending by default
31}
32
33/**
34* @description Constructor accepting sort order as ascending/descending. Use
35* this constructor to *control* the sort order.
36* @param sortOrder
37*/
38public AccountShippingCountryComparator(
39AccountShippingCountryComparator.SortOrder order
40) {
41if (order == null) {
42   throw new ASCComparatorException('Sort order cannot be null');
43 }
44   this.order = order;
45}
46
47public Integer compare(Account a1, Account a2) {
48  Integer compareResult;
49  // Handle null objects before null field values
50  if (a1 == null && a2 == null) {
51    compareResult = 0;
52  } else if (a1 == null) {
53    compareResult = -1;
54  } else if (a2 == null) {
55    compareResult = 1;
56   } else {
57      String a1ShippingCountry = a1?.ShippingCountry;
58      String a2ShippingCountry = a2?.ShippingCountry;
59      // Handle null field values then actual value compares
60      if (a1ShippingCountry == null && a2ShippingCountry == null) {
61            compareResult = 0;
62       } else if (a1ShippingCountry == null) {
63            compareResult = -1;
64       } else if (a2ShippingCountry == null) {
65           compareResult = 1;
66       } else {
67           compareResult = a1.ShippingCountry.compareTo(
68            a2.ShippingCountry
69          );
70       }
71   }
72   if (order == SortOrder.DESCENDING) {
73     return compareResult * -1; // Inverse the sorting if the sorting order is descending
74   }
75    return compareResult;
76   }
77}

The following is example code that shows how to use the above class to sort a list of Accounts using the ShippingCountry field by using comparators.

1public with sharing class ListSortingRecipes {
2    
3    public static void sortAccountsByShippingCountry(List accounts) {
4        accounts.sort(new AccountShippingCountryComparator());
5    }
6
7    public static void sortAccountsByShippingCountryInDescending(
8        List accounts
9    ) {
10        accounts.sort(
11            new AccountShippingCountryComparator(
12                AccountShippingCountryComparator.SortOrder.DESCENDING
13            )
14        );
15    }
16}

You can check complete code with more use cases in the ListSortingRecipes.cls Apex class.

Sort lists based on user locale using the Collator class

Every language has specific rules for sorting or “collating” strings into an ordered list. For instance, in Danish, the letter Æ comes after Z. To ensure proper sorting according to the user’s locale, you can use the new Collator class. The following code example demonstrates how to sort a list of strings based on the user’s locale.

1List shoppingList = new List {
2                'épaule désosé Agneau',
3                'Juice',
4                'à la mélasse Galette 5 kg',
5                'Bread',
6                'Grocery'
7            };
8// Sort based on user Locale
9// Assumes that the user locale is set to France as 'fr_FR'
10// and the code is running in the context of a user with a locale set to 'fr_FR'; 
11 Collator myCollator = Collator.getInstance();
12 shoppingList.sort(myCollator); 
13// The sorted list is in the below order respecting the France locale
14// { 'à la mélasse Galette 5 kg','Bread', 'épaule désosé Agneau', 'Grocery', 'Juice'}

If your business logic relies on a specific sort order, avoid using the Collator class in code that can be executed by users with different locales. The Collator class sorts strings based on the locale, so the sort order may vary for users in different locales.

For loops now support iterables

Previously, if you had an iterable, you needed a more verbose syntax using a while loop, and you explicitly needed to call .iterator(). With Winter ’24, you can now easily iterate through lists or sets using an iterable variable with a for loop syntax.

Let’s take an example Apex class from apex-recipes that used the while loop syntax.

1List records = new List();
2// IterableApiClient implements Iterable
3IterableApiClient client = new IterableApiClient('myNamedCredential');
4Iterator responseIt = client.iterator();
5while (responseIt.hasNext()) {
6 // Calling the Iterator.next() method retrieves a record page with IterableApiClient
7  IterableApiClient.RecordPage page = responseIt.next();
8  records.addAll(page.getRecords());
9}

In the above code, IterableApiClient implements an iterable of the RecordPage type. You can check the complete code in IterableApiClient.cls.

The above code can be simplified with for loop syntax as shown below.

1List records = new List();
2// IterableApiClient implements Iterable
3IterableApiClient client = new IterableApiClient('myNamedCredential');
4for (IterableApiClient.RecordPage page : client) {
5   records.addAll(page.getRecords());
6 }

Check IterationRecipes.cls on how we use the for loop syntax to simplify the code.

Queueable enhancements

Apex queueable gets important improvements in Winter ’24. You can now set the maximum depth for chained queueable jobs, and also ensure that duplicate jobs are not enqueued to reduce race conditions and record locking.

Set maximum depth for chained queueable jobs

If you have implemented retry logic using chained queueable Apex jobs, you may want to limit the number of retry attempts. You can now do this by setting a maximum depth for your chained queueable Apex jobs using the AsyncOptions class (see docs). Use this class to pass as a parameter to the System.enqueueJob() method to define the maximum stack depth for queueable transactions, as well as the minimum queueable delay in minutes.

Note that the ability to set the minimum queueable delay was introduced a few releases ago. Use the minimum queueable delay to avoid busy polling or waiting.

Below is example code that shows how to set the maximum stack depth for your queueable job.

1AsyncOptions options = new AsyncOptions();
2// Guard against run away jobs
3options.MaximumQueueableStackDepth = 200;
4// Avoid busy polling or waiting. Note this was introduced few releases back
5options.MinimumQueueableDelayInMinutes = 2;
6System.enqueueJob(new RecursiveJob(), options);

Prevent duplicate queueable jobs

Duplicate queueable jobs can cause race conditions and record locking. We have seen this scenario when invoking queueable jobs from a trigger, and the trigger executes multiple times causing duplicate jobs.

Use the QueueableDuplicateSignature class (see docs) methods addId(), addInteger(), and addString() to build a unique signature for your queueable job. Use the DuplicateSignature property in the AsyncOptions class to store the queueable job signature.

The following example code shows how to build unique signatures to avoid duplicate queueing of jobs.

1AsyncOptions options = new AsyncOptions();
2// Avoid redundant jobs and contention
3options.DuplicateSignature =     
4    QueueableDuplicateSignature.Builder()
5        .addId(UserInfo.getUserId())
6        .addString('MyQueueable')
7        .build();
8try{
9  System.enqueueJob(new RecursiveJob(), options);
10} catch (DuplicateMessageException ex) {
11    //Exception is thrown if there is already an enqueued job with the same 
12    //signature
13}

Attempting to add more than one Queueable job to the processing queue with the same signature results in a DuplicateMessageException when enqueuing subsequent jobs. This can be handled by the developer as shown in the above code.

Conclusion

The Winter ’24 release of the Salesforce Platform has brought significant improvements for Apex developers. These updates are a testament to Salesforce’s commitment to enhancing the developer experience and streamlining the process of writing business logic in Apex. So, be sure to explore these exciting features and stay ahead in the world of Apex development. Happy coding!

Additional references

About the author

Mohith Shrivastava is a Developer Advocate at Salesforce with a decade of experience building enterprise-scale products on the Salesforce Platform. He is presently focusing on the Salesforce Developer Tools, Apex, and Lightning Web Components at Salesforce. Mohith is currently among the lead contributors on Salesforce Stack Exchange, a developer forum where Salesforce Developers can ask questions and share knowledge. You can follow him on X (Formerly Twitter) and LinkedIn.

More Blog Posts

The Salesforce Developer’s Guide to the Winter ’26 Release

The Salesforce Developer’s Guide to the Winter ’26 Release

Learn about highlights for developers in the Winter '26 release across Lightning Web Components, Apex, Salesforce Platform developer tools, APIs, and more.September 08, 2025