How to get Case IsClosed working inside your Apex triggers

How to get Case IsClosed working inside your Apex triggers

Last week I completed a coding challenge where I needed to know when a Case got closed, and then execute some Apex code before the record was saved. To accomplish this, I used an Apex trigger, checking if the Status value was "Closed" in the before insert and before update triggers.

However, it was pointed out that my solution wouldn't work if there was another status being used for closed Cases. One suggestion was to use the IsClosed field on the Case object, as this is a standard field that will automatically equal true if the status was configured as a closed status.

Screenshot of Case Status configuration in the Salesforce UI
Case Status configuration in the Salesforce UI

The problem

After refactoring my code, I quickly noticed that my triggers were not executing as expected. A few Google searches later, I found out that the reason is that the IsClosed field is a formula field, so does not evaluate until the record is saved to the database. This makes sense in retrospect, but does not seem to be well documented. From my testing, once a record is saved but before being committed (after the before triggers, but before the after triggers), the formula will evaluate as normal.

So how do you get your before insert and before update triggers to know if a Case is closed or not?

The solution

Turns out, there's an Object for that! All that's needed is a simple SOQL query and a little code. Check out the below code snippet.

Case caseRecord = new Case(Status = 'Closed');

List<String> closedStatuses = new List<String>();
for(CaseStatus caseStatus : [SELECT ApiName FROM CaseStatus WHERE IsClosed = TRUE]) {
    closedStatuses.add(caseStatus.ApiName);
}

Boolean isClosed = closedStatuses.contains(caseRecord.Status);
System.debug(isClosed);

What's great about this method is that it also works if you have just instantiated a new Case object, but are yet to insert it into the database (like in my example above). It can even be used with any object since it's just a String comparison, which makes it very versatile - more than the IsClosed field on the Case object.

Taking it one step further

Now, the above code will work just fine in many situations, but what if you have a few different methods that need to call it, in the same transaction? Executing the SOQL query multiple times is bad for performance and will increase the chances of hitting Governor limits, so we need a solution that only executes the query once but is accessible from different methods.

Enter, the Singleton. A Singleton class is a class that can only ever have one instance of the class instantiated (in the Apex execution context). The first time we call the class, a new instance is created. Every other time it's called, this existing instance will be reused. This not only saves SOQL queries, but also reduces the use of other resources like CPU and memory. Take a look at the example below:

public class CaseUtility {
  private static CaseUtility instance = null;
  private List<CaseStatus> caseStatuses = null;

  private CaseUtility() {
    this.caseStatuses = [SELECT Id, ApiName, IsClosed FROM CaseStatus];
  }

  public static CaseUtility getInstance() {
    if (instance == null) {
      instance = new CaseUtility();
    }
    return instance;
  }

  public Boolean isClosed(String status) {
    Boolean isClosed = false;

    for (CaseStatus caseStatus : this.caseStatuses) {
      if (status == caseStatus.ApiName && caseStatus.IsClosed) {
        isClosed = true;
      }
    }

    return isClosed;
  }
}

You can learn more about the Singleton design pattern in Apex here.

The above code demonstrates the following (I may have borrowed some of this explanation from the Salesforce docs):

  • The getInstance() static method will only instantiate an instance of the class if it doesn't already exist in a lazy-initialization manner
  • The constructor and the instance variable for the class is private to make sure that it cannot be instantiated outside of the getInstance() method.
  • The class defines a private, static instance of itself that can only be referenced via the getInstance() static method.
  • The caseStatuses variable stores the result from the SOQL query that is executed by the constructor. In my example it's a private varaible, but could also be public if you wanted.
  • The public method isClosed can access to the private caseStatuses list, and abstracts the required logic to return a simple true or false.

To show this in action, below are two static methods which both need to check if a Case is closed or not. When called (e.g. in a Before Update Trigger), the first one automatically creates a new instance, and the second one automatically re-uses the existing instance. We only performed 1 SOQL query in this example, instead of the 2 SOQL queries required without the Singleton.

public with sharing class CaseTriggerHandler extends TriggerHandler {
    // ... trigger framework

    static void validateBeforeClosing(List<Case> cases) {
        CaseUtility caseUtility = CaseUtility.getInstance();
        for(Case c : cases) {
            Boolean isClosed = caseUtility.isClosed(c.Status);
            if(isClosed) {
                // ... do stuff
            }
        }
    }

    static void addClosedByUserId(List<Case> cases) {
        CaseUtility caseUtility = CaseUtility.getInstance();
        for(Case c : cases) {
            Boolean isClosed = caseUtility.isClosed(c.Status);
            if(isClosed) {
                // ... do other stuff
            }
        }
    }

    public void beforeUpdate(List<SObject> oldRecords, List<SObject> newRecords) {
        validateBeforeClosing((List<Case>) newRecords); // calls the utility class, creates a NEW instance
        addClosedByUserId((List<Case>) newRecords); // calls the utility class, RE-USES the existing instance
    }

}

Wrapping up

That's it! Now you have a simple solution to identify closed Cases in any before insert or before update trigger, without any hard-coded values and only 1 SOQL query. Not only that, but a way to implement it in a scalable and reusable way.

Taking this further still, I am curious to see how this might go as an invokable action inside of a Flow, and find out how Singleton design pattern could be used to improve the functionality and performance of Flows. A quick proof of concept is already showing promise (so long as it's invoked in current transaction)...