Whatever message this page gives is out now! Go check it out!

Asynchronous programming

Last update:
May 18, 2026
In ColdFusion, there is support for asynchronous programming via Future. A Future is an eventual result of an asynchronous operation.
Asynchronous programming is useful when you want to reduce the average response time of an application. You can use asynchronous programming to offload IO or database intensive tasks. Also, use asynchronous programming to improve the responsiveness of a UI.
Some of the benefits of asynchronous programming are:
  • Near real-time processing
  • Easy to distribute tasks
  • Uses its own worker threads
  • Dynamically configurable thread pool (Admin Console)
  • On-demand thread pool creation
For asynchronous programming, you can use the runAsync function .
Example
When salary is credited to our bank accounts, we pay various bills like credit cards, mortgage, utilities, and so on. The payments are dependent on salary or in this context, chained to salary.
<cfscript>

 getAccountBalance = function(){
            var balance = 120000;
            return balance;
 }

 function payCreditCardBill(accountBalance){
            var ccBill = 1890;
            return accountBalance-ccBill;
 }

 payEMIs = function(accountBalance){
            var mortgageEMI = 1000;
            var carLeaseEMI = 750;
            var healthInsuranceEMI = 250;

            return accountBalance-(mortgageEMI+carLeaseEMI+healthInsuranceEMI);
 }

 miscellenousExpenses = function(accountBalance){
            var shopping = 1500;
            var clubExpense  =1000;
            var casinoExpense = 2000;
            return accountBalance-(shopping+clubExpense+casinoExpense);
 }

 checkBalance = function(accountBalance){
            while(accountBalance > 5000){
                        accountBalance = miscellenousExpenses(accountBalance);
                                    }
            if(accountBalance < 5000)
                        throw (message="Account balance below threshold!!!", type="info");
 }

 errorHandler = function(error){
            if(error.message contains "Account balance below threshold!"){
                        return "You have reached your spending limit!";
            }
 }

 future = runAsync(getAccountBalance).then(payCreditCardBill).then(payEMIs).
 then(miscellenousExpenses).then(checkBalance).error(errorHandler);

 writelog(future.get());

</cfscript>
You can also use closures with the runAsync function.
For example,
<cfscript>
            future = runAsync(function(){return "I am invoked from RunAsync directly!";});
</cfscript>
Methods available with runAsync are:
  • cancel();
  • error(callback, timeout);
  • error(callback);
  • get();
  • get(timeout
  • isCancelled();
  • isDone();
  • then(callback);
  • then(callback, timeout);

Empty Future

An empty future is an object, which can be explicitly marked as complete with a result value. It can be used in producer/consumer scenarios.
For example,
<cfscript>
 p = runAsync(); // empty future
 p.complete(10); 
 writelog(p.get()); // displays 10
</cfscript>
The methods available on an empty Future are:
  • cancel()
  • get()
  • isCancelled()
  • isDone()
  • complete(value)

Executor Pool Configuration

In the Server Settings section in the Administrator, there is an option Executor Pool Configuration , which enables you to specify values for:
  • Core pool size: Core pool size is the minimum number of worker threads to keep alive. The value should be less than the value specified in Maximum Pool Size. The default value is 25.
  • Maximum pool size: Maximum number of threads that can ever be available in the pool. The default value is 50.
  • Keep alive time: Timeout in milliseconds for idle threads waiting for work. Threads use this timeout when there are more than the corePoolSize present in the pool. The default value is 2000 ms.
Figure: Executor Pool Configuration
Executor Pool Configuration
These settings enable you to finetune your async executor according to your requirements. Also, these property changes take effect without any server restart.
We have also added the following Admin APIs to support the properties mentioned above. These APIs are a part of runtime.cfc.
In the 2018 release of ColdFusion, to support the pool configuration settings, we have also added three new properties to the API, getRuntimeProperty(required propertyName). They are:
  • corePoolSize
  • maxPoolSize
  • keepAliveTime
For example,
<cfscript>
    // Login is always required.
    adminObj = createObject("component","cfide.adminapi.administrator");
    adminObj.login("admin");
    runtimeObj=createObject("component","cfide.adminapi.runtime");
    corePool=runtimeObj.getRuntimeProperty("corePoolSize");
    writeOutput("core pool size is: " & corePool & "<br/>");
    maxPool=runtimeObj.getRuntimeProperty("maxPoolSize");
    writeOutput("max pool size is: " & maxPool & "<br/>");
    keepAlive=runtimeObj.getruntimeProperty("keepAliveTime");
    writeOutput("keep alive time is: " & keepAlive & "<br/>");
</cfscript>

Async futures in ColdFusion 2025 Update 8

ColdFusion 2025 Update 8 aligns the CFML Future type with CompletableFuture, replacing blocking stage execution with callback-based composition to improve thread pool efficiency under load.

Background

Older builds could block worker threads while chained futures waited on earlier work. The runtime sometimes called get() on a prior stage from inside a worker that was supposed to run the next step, tying up that thread until the dependency finished. Under load, the pool could run out of idle workers and throughput dropped even when CPUs had spare capacity.
Update 8 aligns the CFML Future type with CompletableFuture. Stages can be wired with callbacks that return the thread to the pool instead of parking it on get(). You still use runAsync(), then(), and error() as before. You can add coordination helpers, timeouts, and explicit sync or async stage methods when you need finer control.

What a future represents

A Future is a handle for work that may still be running. Callers can chain follow-up logic, apply timeouts, or wait with get(). After Update 8, that handle participates in the same completion-stage model Java code uses with CompletableFuture.

Inheritance from CompletableFuture

ColdFusion Future extends CompletableFuture<Object>. Any API that accepts a CompletableFuture can receive a CF Future. You can also call Java stage methods directly on the same object. The type relationship is one way: every CF Future is a CompletableFuture, but a CompletableFuture created in Java is not automatically a CF Future.

Scheduling and the thread pool

New async work is submitted through paths aligned with CompletableFuture execution — for example, async supplier-style scheduling on the ColdFusion executor. Chaining via CF then() is implemented with non-blocking composition internally instead of blocking on the previous result inside the worker.
Long chains of runAsync plus then are less likely to pin many pool threads in a wait state. You still need to avoid calling get() inside small callbacks if those callbacks should stay non-blocking. Reserve get() for request boundaries, tests, or places where blocking is intentional.

Sync versus async follow-up stages

Use synchronous methods when the next step is short and should run immediately after the prior stage completes, on the same completion thread. Examples include formatting a string, picking a branch flag, or copying a few fields into a struct.
Use *Async methods when the follow-up may run for a long time, performs I/O, or should not occupy the completing thread. The executor runs the callback later. More hand-offs occur, so use async stages when the work justifies the extra scheduling cost.
CF then() remains the fluent continuation many apps already use. It is implemented with non-blocking composition in this update. When you need Java-named operations — for example thenCombine with two futures — you can call them on the same object. Keep chaining style consistent so you do not lose CF-only methods mid-pipeline.

Coordination APIs

asyncAllOf(array)
Waits for every future in the array. If any input fails, the combined outcome reflects failure. Pass only CF Future instances. Typical use: flush parallel HTTP calls, file reads, or service checks, then merge results in a final then().
asyncAnyOf(array)
Completes when the first future in the array completes; the combined future takes that stage's outcome (value or failure). Use for redundant data sources, cache versus origin races, or hedged requests when you only need one good answer.
thenCombine / thenCombineAsync
When you have exactly two futures and want a single derived value, avoids manual get() pairing. The combiner receives both results when both succeed.
applyToEitherAsync / acceptEitherAsync
Run when either this future or the other completes first (normally). Use when two paths produce the same kind of value and the fastest success should win.
runAfterEitherAsync
Runs a parameterless action after the first completion when you do not need the value in that step.

Timeout APIs

orTimeout(timeoutMs)
Fails the stage if it does not finish in the specified number of milliseconds. Use when a late answer has no business value and should surface to monitoring or a fallback path. Attach timeouts before calling get(); if you call get() first on an unbounded stage, the caller waits without the timeout guard.
completeOnTimeout(value, timeoutMs)
Completes normally with the supplied value when the deadline passes. Use for soft degradation: cached data, empty lists, or placeholder text for UI or APIs.

Async stage methods

thenApplyAsync(fn)
Maps a completed value using a plain function from value to value (does not return a Future). Runs on the async executor.
thenComposeAsync(fn)
Use when the next step returns another Future; flattens nested futures. Runs on the async executor.
thenCombineAsync(other, fn)
Combines two independent futures and derives a single value on the async executor.
Note: Synchronous counterparts — thenApply, thenCompose, handle, whenComplete, and others — run on the completing thread and add less scheduling overhead when the follow-up is trivial.

Error observation and recovery APIs

handle(fn) / handleAsync(fn)
Receives the value and an error object (one is null on each path) and returns a single replacement value. Use when every completion should collapse to one type, including fallback values on failure.
exceptionally(fn) / exceptionallyAsync(fn)
Use when the success path should pass through unchanged and only failures need mapping to a recovery value.
exceptionallyCompose(fn) / exceptionallyComposeAsync(fn)
Maps a failure to a new Future for recovery. Use when recovery itself is asynchronous.
whenComplete(fn) / whenCompleteAsync(fn)
Runs a side effect with result and error. Use for logging, metrics, or cleanup that must run on both success and failure. Does not replace the result; it observes completion. Prefer handle when the next stage must always produce a new value.

Code examples

Sequential pipeline with thenComposeAsync:
userIdFuture = runAsync( function() { return 42; } );
profileFuture = userIdFuture.thenComposeAsync( function( id ) {
    return runAsync( function() {
        // load profile by id
        return { id: id, name: "Example" };
    } );
} );
writeOutput( serializeJSON( profileFuture.get() ) );
// Output: {"ID":42,"NAME":"Example"}
Map a value with thenApplyAsync:
runAsync( function() { return 10; } )
    .thenApplyAsync( function( n ) { return n * 2; } )
    .then( function( n ) { writeOutput( n ); } );
Wait for all with asyncAllOf:
f1 = runAsync( function() { return 1; } );
f2 = runAsync( function() { return 2; } );
allDone = asyncAllOf( [ f1, f2 ] );
allDone.then( function() {
    total = f1.get() + f2.get();
    writeOutput( total ); // 3
} );
Race with asyncAnyOf:
f1 = runAsync( function() { sleep( 300 ); return "slow"; } );
f2 = runAsync( function() { return "fast"; } );
first = asyncAnyOf( [ f1, f2 ] );
writeOutput( first.get() ); // fast
Fail on timeout with orTimeout:
slow = runAsync( function() { sleep( 5000 ); return "done"; } ).orTimeout( 3000 );
try {
    writeOutput( slow.get() );
} catch ( any e ) {
    writeOutput( "Timed out" );
}
Return a default on timeout with completeOnTimeout:
slow = runAsync( function() { sleep( 6000 ); return "too late"; } )
    .completeOnTimeout( "DEFAULT", 5000 );
writeOutput( slow.get() ); // DEFAULT
Java interop with CompletableFuture.allOf:
future1 = runAsync( function() { return "Hello"; } );
future2 = runAsync( function() { return "World"; } );
CompletableFutureClass = createObject( "java", "java.util.concurrent.CompletableFuture" );
combined = CompletableFutureClass.allOf( [ future1, future2 ] );
combined.get();
writeOutput( future1.get() & " " & future2.get() ); // Hello World

Java and CF pipeline compatibility

You can pass a CF Future where a CompletableFuture is expected and call Java stage methods on a CF Future. However, asyncAllOf() and asyncAnyOf() expect an array of CF Future values. A plain Java CompletableFuture can produce a type error because those helpers are typed to CF futures, not every Java completion stage.
Avoid mixing CF then() and Java thenApply() in one chain. Calling thenApply on a CF Future returns a CompletableFuture view that is not guaranteed to expose CF-only methods such as then(). Pick one style per pipeline segment. A safe pattern is to finish CF-specific chaining, then treat the remainder as Java-only, or copy the result into a new runAsync if you must return to CF helpers.
Prefer runAsync() for CF-authored tasks. CompletableFuture.supplyAsync with a CF closure can fail when the runtime cannot create the required functional-interface proxy. If a call fails at runtime when passing CF closures where Java expects certain functional interfaces, move the body into runAsync and chain with CF-friendly methods instead.

Implementation checklist

  1. Start each parallel branch with runAsync() so you hold real Future instances.
  2. Choose asyncAllOf or asyncAnyOf when the arity is a list. Choose thenCombine when you have exactly two futures and a merge function.
  3. Put timeouts on stages that call remote systems or user code with unbounded work.
  4. Avoid get() inside chained callbacks unless you accept blocking that callback.
  5. Keep exception types in mind for get() versus join() when you read Java docs; CF callers often use get() with try or catch.
  6. After you call Java-only chain methods, assume the returned object is a Java completion stage unless you verify otherwise.

Share this page

Was this page helpful?
We're glad. Tell us how this page helped.
We're sorry. Can you tell us what didn't work for you?
Thank you for your feedback. Your response will help improve this page.

On this page