- The callout is sent using a Continuation object.
- The server thread is released.
- When the external system replies, Salesforce rehydrates the Apex transaction and processes the response using a callback.
What Is a Continuation in Apex?
Let’s start simple. A continuation in Apex is Salesforce’s way of letting you call out to external services without making your users wait around. Normally, when you make an HTTP callout in Apex, it’s synchronous - you send a request, wait, and the user sits twiddling their thumbs until it comes back. Continuations flip that on its head.
Instead of holding everything up, a continuation says, “Hey, Salesforce, I’ll handle this later. Go do your thing, and I’ll catch up when the response gets here.” Think of it like ordering coffee and stepping aside until your name’s called, rather than waiting awkwardly in front of the cashier.
Here's a bare-bones example:
public class ContinuationExample {
/*
* This method is exposed to Aura and uses Continuation for async callout.
*
* Note: The Continuation pattern is used for long-running callouts. The platform will automatically
* resume execution by calling the method specified in continuationMethod when the response is ready.
*/
@AuraEnabled(Continuation = true)
public static Object makeCallout() {
/**
* Creates a Continuation object with a 5-second timeout.
* The 5-second timeout parameter specifies the maximum amount of time (in seconds)
* that the server will wait for the asynchronous callout to complete before timing out.
* This helps ensure that long-running callouts do not block server resources indefinitely.
*/
Continuation cont = new Continuation(5);
/**
* The continuationMethod property tells the platform which method to invoke
* when the asynchronous callout completes. This must match the name of your response handler method.
* Here, it is set to 'processResponse', so the platform will call processResponse when the HTTP response is ready.
* This is a critical step for Continuation to work correctly.
*/
cont.continuationMethod = 'processResponse';
System.debug('Created Continuation with 5-second timeout');
HttpRequest req = new HttpRequest();
// Set the endpoint for the callout (simulates a delayed response)
req.setEndpoint('https://forceshark.com/api/v1/hello-world/continuation-demo?delayMs=10000');
req.setMethod('GET');
System.debug('Prepared HTTP request for endpoint: ' + req.getEndpoint());
/**
* When you add an HTTP request to the Continuation, you receive a request label (a String).
* This label uniquely identifies the request and is used later to retrieve the corresponding response.
* You must use the same label in Continuation.getResponse(label) to get the correct HttpResponse.
*/
String requestLabel = cont.addHttpRequest(req);
System.debug('Added HTTP request to Continuation. Request label: ' + requestLabel);
// Store user info and request label in a map, then serialize to JSON for cont.state
Map<String, Object> stateMap = new Map<String, Object>{
'userId' => UserInfo.getUserId(),
'userName' => UserInfo.getName(),
'requestLabel' => requestLabel
};
/**
* The cont.state property allows you to store any serializable object or value
* that you want to persist between the initial callout and the response handling.
* For example, you might store user context, parameters, or any data you need
* when processing the response.
* In a real scenario, you would access cont.state in the response handler to restore context.
*/
cont.state = JSON.serialize(stateMap);
System.debug('Set cont.state to serialized user info and request label: ' + JSON.serializePretty(stateMap));
// Return the Continuation object to the platform, which will resume when the response is ready
return cont;
}
/*
* This method processes the response from the async callout.
*
* Note: @AuraEnabled(Cacheable=true) is used for methods that do not modify data and can be cached on the client side.
* However, processResponse retrieves dynamic data from an async callout, so it should NOT be marked as Cacheable=true.
*/
@AuraEnabled
public static Object processResponse(String stateJson) {
// Deserialize the state JSON to retrieve user info and request label
Map<String, Object> stateMap = (Map<String, Object>) JSON.deserializeUntyped(stateJson);
System.debug('Deserialized cont.state: ' + stateMap);
String requestLabel = (String) stateMap.get('requestLabel');
System.debug('Using requestLabel from state: ' + requestLabel);
HttpResponse res = Continuation.getResponse(requestLabel);
System.debug('Received HTTP response. Status: ' + res.getStatus());
System.debug('Response body: ' + res.getBody());
return res.getBody();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<RemoteSiteSetting xmlns="http://soap.sforce.com/2006/04/metadata">
<disableProtocolSecurity>false</disableProtocolSecurity>
<isActive>true</isActive>
<url>https://forceshark.com</url>
</RemoteSiteSetting>
In this code, we send off a request and register a handler to process the response when it comes back.
The Problem with Traditional Callouts
You ever waited on a slow API? Painful, right? That’s exactly what happens with traditional Apex callouts. Since Apex runs on Salesforce's multi-tenant architecture, there's a tight leash on how long your operations can run. A callout that's too slow can throw a timeout and crash your process - bad news for your user and your code.
Synchronous callouts tie up the server thread. It’s like calling a friend who takes forever to answer while your boss stares at you, waiting. Not ideal.
This becomes an even bigger problem in Lightning components or Experience Cloud pages where user experience matters. Every second of delay increases the chance that a user clicks away.
How Continuations Solve These Problems
Continuations make your apps feel snappier and smarter. Instead of waiting, your logic keeps flowing. Salesforce frees up the processing thread while your external callout does its thing in the background. Once the response comes back, the process picks up exactly where it left off.
It's kind of like putting your laundry in the machine and going off to cook dinner. When it buzzes, you're ready to fold. No time wasted.
Technically, it works like this:
It’s elegant. Efficient. And your users? They’re not staring at spinning wheels.
Real-World Scenarios Where Continuations Shine
Let’s talk about where this really pays off.
Tips for Using Continuations Effectively
getResponse()
.Common Pitfalls to Avoid
Conclusion
At the end of the day, Continuations in Apex aren’t just a fancy trick - they’re a vital tool for building modern, scalable, user-friendly apps on Salesforce. They take the pain out of slow callouts and bring back control to your code. If you’re serious about improving performance, especially in Lightning apps and portals, it's time to master this pattern.
Think of Continuations as your backstage pass to smoother UI experiences. They won’t solve every problem, but when the use case is right, they can be game-changing.
So next time your Apex starts stalling over an HTTP request, ask yourself - why wait when you can continue?