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

Use built-in functions as callbacks in ColdFusion

Last update:
May 18, 2026
Learn when you can pass a built-in function directly as a callback, when to use a UDF or closure, and how to pass Java SAM instances to ColdFusion higher-order operations.

Introduction

ColdFusion has a large set of built-in functions (BIFs) , ucase, trim, isNumeric, max, compare, and many more, that you use every day. Modern CFML also has higher-order APIs: arrayMap, listFilter, arraySort, and so on, which take a callback (a function) to describe what to do to each item. This guide explains when you can pass a BIF directly as that callback, when you must use a UDF or closure instead, and how to write reproducible cfscript for each case.
Starting with ColdFusion 2025, callbacks can also be Java SAM (Single Abstract Method) instances, including Java classes you define inline in CFML using the new java{} block, letting you wrap stateful, performance-critical, or library-backed logic in Java and pass it straight to BIFs like arrayMap, arrayFilter, and arrayEach.

Problem statement

For a long time, the engine treated BIF references and user-defined function (UDF) references as different internal types. APIs that expected a "function" for map, filter, or reduce often only accepted UDFs or anonymous functions. If you passed a BIF by name — for example a.map(ucase) — you could get a runtime error such as:
Cannot cast coldfusion.runtime.CFPageMethod to coldfusion.runtime.UDFMethod
That forced wrapping a BIF in another function just to satisfy the type system — for example a.map(function (s) { return ucase(s); }), even though ucase already is the transform you want. The same issue appeared when you passed callbacks to your own function parameters, stored callables in arrays, or built data pipelines.
This feature addresses that gap: where the language can unambiguously call a BIF as a callback, it behaves like any other function value.

What the feature does

The feature unifies callable values for supported higher-order operations:
  • You can pass a BIF where a callback is required — for example ArrayMap(arr, ucase) or arr.filter(isNumeric).
  • You can assign a BIF to a variable and pass that variable: fn = ucase; arr.map(fn).
  • You can put BIFs in arrays or structs (then retrieve into a variable and call) for pluggable behavior.
  • You can pass a BIF to custom functions that declare a function parameter, the same as a UDF.
  • For array and list operations, BIFs are generally interchangeable with UDFs and closures when the arity and meaning of the callback match what the operation passes.
  • You can pass a Java SAM instance (any Java object whose class implements a single-abstract-method interface such as java.util.function.Function, Predicate, Consumer, BiFunction, BiPredicate, BiConsumer, or BinaryOperator) directly as a callback to BIFs whose callback arity matches the SAM's abstract method.
  • You can pass a <cffunction> page method (CFPageMethod) — including in parallel=true mode — for Array, List, and Query operations. Struct and Query operations reject CFPageMethod (key/value ambiguity) but accept their Java SAM equivalents (BiFunction / BiPredicate) because the SAM signature itself disambiguates.

Benefits of the feature

Each benefit reduces boilerplate and improves pipeline readability.
BenefitWhat you get
Less noiseWrite a.map(ucase) instead of a.map(function (s) { return ucase(s); }) wherever the logic is identical.
Easier pipelinesChains such as raw.map(trim).filter(isNumeric) read clearly without nested function blocks.
Familiar styleOther JVM and functional languages routinely pass library functions as callbacks; ColdFusion aligns with that pattern for BIFs.
Pluggable behaviorStore [ucase, lcase, trim] or pass isNumeric into a generic helper without redundant wrappers.
Predictable refactoringWhere supported, BIFs and equivalent UDFs yield the same results — easier code review and fewer subtle differences.
Use BIF callbacks when the callback signature matches what the operation supplies. Use a UDF or closure when you need extra parameters, lexical closure, or struct/query semantics that require an explicit (key, value) or (row) function.

Arrays: all built-in function callback operations

For array and list Java SAM examples, see Java SAM instances as ColdFusion callbacks.
ArrayMap
result = ArrayMap(["hello", "world"], ucase);
writeDump(result); // ["HELLO","WORLD"]

// Member function
a = ["hello", "world"];
result = a.map(ucase);
writeDump(result); // ["HELLO","WORLD"]

// Example
rawSkus = [" sku-001 ", "SKU-002", " sku-003"];
normalized = rawSkus.map(trim).map(ucase);
writeDump(normalized);
ArrayFilter
result = ArrayFilter(["123", "abc", "456"], isNumeric);
writeDump(result); // ["123","456"]

// Example
cells = ["12", "N/A", "3", ""];
ints = cells.filter(isNumeric).map(val);
writeDump(ints); // [12, 3]
ArrayEvery
allNums = ArrayEvery(["123", "456", "789"], isNumeric);
writeOutput(allNums); // true
ArraySome
hasNum = ArraySome(["abc", "123", "def"], isNumeric);
writeOutput(hasNum); // true
ArrayEach — invokes the callback without building a new array. Useful with logging or pushing to scope (use carefully in parallel).
ArrayEach(["a", "b"], ucase);
writeOutput("done"); // "done" (ArrayEach is void, no return)
ArrayReduce
nums = [3, 7, 2, 9, 5];
top = ArrayReduce(nums, max, 0);
writeOutput(top); // 9

// Member function
bottom = nums.reduce(min, 100);
writeOutput(bottom); // 2

// Example
temps = [72, 68, 75, 71, 80];
peak = temps.reduce(max, -274);
writeOutput(peak); // 80
ArrayFind
fns = [ucase, lcase, trim];
idx = ArrayFind(fns, lcase);
writeOutput(idx); // 2
ArraySort — comparator built-in functions
ArraySort expects a binary comparator: (a, b) → negative, zero, positive.
fruit = ["banana", "apple", "cherry"];
ArraySort(fruit, compare);
writeOutput(arrayToList(fruit)); // apple,banana,cherry

mixed = ["Banana", "apple", "Cherry"];
mixed.sort(compareNoCase);
writeOutput(arrayToList(mixed)); // apple,Banana,Cherry

Lists: all built-in function callback operations

Lists are delimited strings. Default delimiter is comma unless you pass another.
ListFilter
csv = "123,abc,456,def";
onlyNums = ListFilter(csv, isNumeric);
writeOutput(onlyNums); // 123,456

// Custom delimiter
pipe = "123|abc|456";
pipeNums = ListFilter(pipe, isNumeric, "|");
writeOutput(pipeNums); // 123|456
ListMap
out = ListMap("hello,world", ucase);
writeOutput(out); // HELLO,WORLD
ListEvery and ListSome
writeOutput(ListEvery("123,456,789", isNumeric)); // true
writeOutput(ListSome("abc,123,def", isNumeric));  // true
ListEach
ListEach("a,b,c", ucase);
writeOutput("done"); // "done" (ListEach is void, no return)
ListReduce
nums = "3,7,2,9,5";
writeOutput(ListReduce(nums, max, 0)); // 9

// Semicolon delimiter
writeOutput(ListReduce("3;7;2;9;5", max, 0, ";")); // 9

Strings: ASCII callback values

For string higher-order functions, callbacks operate per character. Values passed to numeric BIFs like max or min are typically ASCII (or Unicode) integer codes, not one-character strings.
StringReduce with max
digits = "35729";
n = StringReduce(digits, max, 0);
writeOutput(n); // 57 — ASCII code of '9', not the number 9
Digit-wise maximum
digits = "35729";
digitMax = StringReduce(digits, function (prev, code) {
    ch = chr(code);
    d = val(ch);
    return max(prev, d);
}, 0);
writeOutput(digitMax); // 9

Structs — bare built-in functions and CFPageMethod rejected; UDF wrappers and Java SAMs accepted

Struct higher-order BIFs reject bare CF BIFs and <cffunction> page methods because the runtime cannot infer whether the callback should run on keys or values. Two callable shapes are accepted:
  • A CF UDF or closure with the operation's documented signature — (key, value) for filter/each/every/some/map; (acc, key, value) for reduce.
  • A Java SAM whose abstract method names both arguments — see built-in-functions-as-callbacks.dita#struct-sams.
StructFilter — filter by numeric keys
s = { "1": "one", "abc": "two", "3": "three" };
filtered = StructFilter(s, function (key, value) {
    return isNumeric(key);
});
writeOutput(structCount(filtered)); // 2
StructMap — transform values to upper case
s = { "a": "hello", "b": "world" };
out = StructMap(s, function (key, value) {
    return ucase(value);
});
writeDump(out); // { "a": "HELLO", "b": "WORLD" }
StructReduce — numeric max over keys with UDF
s = { "3": "a", "7": "b", "2": "c" };
maxKey = StructReduce(s, function (acc, key, value) {
    return max(acc, val(key));
}, 0);
writeOutput(maxKey); // 7

Queries: bare built-in functions and CFPageMethod rejected; UDF wrappers and Java SAMs accepted

Query higher-order BIFs reject bare CF BIFs and <cffunction> page methods because the runtime cannot infer which column the BIF should target. Accepted shapes:
  • A CF UDF or closure that takes the row struct as its argument.
  • A Java SAM (Predicate / Function / Consumer / BiFunction)
Note: QuerySort accepts only CF closures and UDFs in this release; java.util.Comparator is not yet wired up.
QueryFilter — keep rows where predicate on row struct holds
q = queryNew("name,age", "varchar,integer", [
    ["John", 30], ["Jane", 25], ["Bob", 35]
]);
young = QueryFilter(q, function (row) {
    return row.age < 32;
});
writeOutput(young.recordCount); // 2
QueryReduce — max score with max inside UDF
q = queryNew("score", "integer", [[90], [85], [95]]);
top = QueryReduce(q, function (prev, row) {
    return max(prev, row.score);
}, 0);
writeOutput(top); // 95

BIFs as first-class values

Assign to a variable
myFunc = ucase;
a = ["hello", "world"];
writeDump(a.map(myFunc)); // ["HELLO","WORLD"]
Store in array — pipeline of transforms
transformers = [ucase, lcase, reverse];
input = "Hello";
results = [];
for (fn in transformers) {
    arrayAppend(results, fn(input));
}
writeOutput(arrayToList(results)); // HELLO,hello,olleH
Store in struct — retrieve then call
Direct ops["upper"]("x") may not parse; retrieve into a variable first.
ops = { upper: ucase, lower: lcase, rev: reverse };
fn = ops["upper"];
writeOutput(fn("hello")); // HELLO
Pass to custom function with function parameter type
function applyAll(required array arr, required function fn) {
    var out = [];
    for (item in arr) {
        arrayAppend(out, fn(item));
    }
    return out;
}
writeDump(applyAll(["a", "b"], ucase)); // ["A","B"]

Interchangeability: BIF, UDF, closure

For array one-argument callbacks, these are equivalent in outcome:
a = ["hello", "world"];
r1 = a.map(ucase);

function myUcase(s) {
    return ucase(s);
}
r2 = a.map(myUcase);

r3 = a.map(function (s) {
    return ucase(s);
});

writeOutput(arrayToList(r1) == arrayToList(r2) && arrayToList(r2) == arrayToList(r3)); // true
For parallel callback support, see built-in-functions-as-callbacks.dita#parallel-mode.

Java SAM instances as ColdFusion callbacks

For the inverse direction, passing a CF closure where a Java API expects a SAM- see Use ColdFusion callbacks where Java APIs expect a SAM.
ColdFusion 2025 lets you pass a Java SAM (Single Abstract Method) instance directly to any CF higher-order BIF whose callback signature matches the SAM's abstract method. This is the natural way to reuse Java helpers — validators, transformers, comparators — inside ColdFusion data pipelines.
Mapping ColdFusion callback shapes to Java SAM types and their abstract methods.
Callback shapeJava SAMAbstract method
(value) -> Rjava.util.function.FunctionObject apply(Object)
(value) -> booleanjava.util.function.Predicateboolean test(Object)
(value) -> voidjava.util.function.Consumervoid accept(Object)
(a, b) -> Rjava.util.function.BiFunctionObject apply(Object, Object)
(a, b) -> booleanjava.util.function.BiPredicateboolean test(Object, Object)
(a, b) -> voidjava.util.function.BiConsumervoid accept(Object, Object)
(a, a) -> ajava.util.function.BinaryOperatorObject apply(Object, Object)
Array
// ArrayFilter + Predicate, ArrayReduce + BinaryOperator
predicateClass = java {
    public class EvenPredicate implements java.util.function.Predicate {
        public EvenPredicate() {}
        public boolean test(Object value) {
            return ((java.lang.Number) value).intValue() % 2 == 0;
        }
    }
};

opClass = java {
    public class MaxOperator implements java.util.function.BinaryOperator {
        public MaxOperator() {}
        public Object apply(Object a, Object b) {
            int x = ((java.lang.Number) a).intValue();
            int y = ((java.lang.Number) b).intValue();
            return java.lang.Integer.valueOf((x > y) ? x : y);
        }
    }
};

nums = [3, 7, 2, 9, 5, 8, 4];
evens = ArrayFilter(nums, predicateClass.init());
peakEven = ArrayReduce(evens, opClass.init(), 0);
writeOutput("evens=" & arrayToList(evens) & ", peak=" & peakEven);
// evens=2,8,4, peak=8
// ArraySort + java.util.Comparator
// Note: ArraySort accepts Comparator; QuerySort does NOT.
arr = ["banana","fig","apple","kiwi"];
cmpClass = java {
    public class StrLenCmp implements java.util.Comparator {
        public StrLenCmp() {}
        public int compare(Object a, Object b) {
            return a.toString().length() - b.toString().length();
        }
    }
};
ArraySort(arr, cmpClass.init());
writeOutput(arrayToList(arr)); // fig,kiwi,apple,banana
List
// ListEach + Consumer (counts elements)
consumerClass = java {
    public class CountingConsumer implements java.util.function.Consumer {
        public int count = 0;
        public CountingConsumer() {}
        public void accept(Object value) { count = count + 1; }
    }
};

consumer = consumerClass.init();
ListEach("apple,banana,cherry,date", consumer);
writeOutput(consumer.count); // 4
String
Important: String callbacks pass each character as a java.lang.Character, not a one-character String. SAM implementations must cast to Character. For StringReduce + BinaryOperator, the accumulator must remain Character-compatible — return Character.valueOf(...), otherwise the runtime throws ClassCastException between iterations.
isDigit = java {
    public class IsDigit implements java.util.function.Predicate {
        public IsDigit() {}
        public boolean test(Object value) {
            char c = ((java.lang.Character) value).charValue();
            return c >= '0' && c <= '9';
        }
    }
};

maxChar = java {
    public class MaxChar implements java.util.function.BinaryOperator {
        public MaxChar() {}
        public Object apply(Object a, Object b) {
            char x = ((java.lang.Character) a).charValue();
            char y = ((java.lang.Character) b).charValue();
            return java.lang.Character.valueOf((x > y) ? x : y);
        }
    }
};

digits  = StringFilter("a1b2c3", isDigit.init());
biggest = StringReduce("bdcae", maxChar.init(), javacast("char", "a"));
writeOutput("digits=" & digits & ", biggest=" & biggest);
// digits=123, biggest=e
Struct
Struct higher-order BIFs reject bare CF BIFs and <cffunction> page methods. Java SAMs are accepted because the SAM signature is explicit.
Java SAM types accepted by each Struct higher-order operation.
Struct operationSAMSignature
StructFilter, StructEvery, StructSomeBiPredicateboolean test(key, value)
StructEachBiConsumervoid accept(key, value)
StructMapBiFunctionObject apply(key, value)
StructReduceBiFunctionObject apply(acc, java.util.Map.Entry) — call getKey() / getValue() inside the SAM
Note: Custom 3-arg interfaces are explicitly rejected by StructReduce with "Callback must be a UDFMethod or BiFunction".
// StructSome + BiPredicate
isPositive = java {
    public class IsPositive implements java.util.function.BiPredicate {
        public IsPositive() {}
        public boolean test(Object key, Object value) {
            return (value instanceof java.lang.Number)
                && ((java.lang.Number) value).doubleValue() > 0;
        }
    }
};

hasPositive = StructSome({a:-1, b:-2, c:3, d:-4}, isPositive.init());
writeOutput(hasPositive); // YES
// StructReduce + BiFunction(acc, Map.Entry) — sum values
sumValues = java {
    public class SumValues implements java.util.function.BiFunction {
        public SumValues() {}
        public Object apply(Object acc, Object entry) {
            int a = ((java.lang.Number) acc).intValue();
            java.util.Map.Entry e = (java.util.Map.Entry) entry;
            int v = ((java.lang.Number) e.getValue()).intValue();
            return java.lang.Integer.valueOf(a + v);
        }
    }
};

total = StructReduce({a:1, b:2, c:3, d:4, e:5}, sumValues.init(), 0);
writeOutput(total); // 15
Query
Query callbacks receive each row as a java.util.Map; column names are upper-cased keys. As with Struct, bare CF BIFs and <cffunction> are rejected; Java SAMs are accepted.
Java SAM types accepted by each Query higher-order operation.
Query operationSAMSignature
QueryFilter, QueryEvery, QuerySomePredicateboolean test(row)
QueryEachConsumervoid accept(row)
QueryMapFunctionObject apply(row)
QueryReduceBiFunctionObject apply(acc, row)
QuerySortCF closure / UDF onlyjava.util.Comparator is not accepted in this release
// QueryReduce + BiFunction (sum of an integer column)
sumAges = java {
    public class SumAges implements java.util.function.BiFunction {
        public SumAges() {}
        public Object apply(Object acc, Object row) {
            int a = ((java.lang.Number) acc).intValue();
            java.util.Map m = (java.util.Map) row;
            int v = ((java.lang.Number) m.get("AGE")).intValue();
            return java.lang.Integer.valueOf(a + v);
        }
    }
};

q = queryNew("name,age", "varchar,integer", [["A",25],["B",35],["C",45]]);
writeOutput(QueryReduce(q, sumAges.init(), 0)); // 105
Parallel mode
Every parallel-capable BIF (ArrayEach/Map/Filter/Some/Every, StructEach/Filter/Map, QueryEach/Filter) accepts the same callback shapes the sequential variant does — including a <cffunction> defined in the calling page (CFPageMethod). maxThreadCount must be 1–50; out-of-range values throw at the call site. Use java.util.concurrent.atomic.AtomicInteger (or similar) inside parallel SAM callbacks if you need shared state.
Known limitations in this build:
  • ArrayMap(arr, javaFunction, true) returns an empty array. Workaround: use a CF closure for the parallel path.
  • QueryFilter(q, javaPredicate, true) throws ClassCastException. Workaround: use a CF closure for the parallel path.
  • QuerySort does not accept java.util.Comparator in any mode — closure / UDF only.
<!--- cffunction as a parallel ArrayEach callback --->
<cfset arrayEach( ["a","b","c","d","e"], foo, true, 5 )>

<cffunction name="foo" returnType="void" output="false">
    <cfargument name="element" type="string" required="true">
    <cfquery name="local.r" dbtype="query">
        SELECT 1 AS x FROM (SELECT 1 AS x) data
    </cfquery>
</cffunction>
// Parallel + Java Consumer with thread-safe counter
counter = java {
    public class AtomicCountingConsumer implements java.util.function.Consumer {
        public java.util.concurrent.atomic.AtomicInteger n
            = new java.util.concurrent.atomic.AtomicInteger(0);
        public AtomicCountingConsumer() {}
        public void accept(Object value) { n.incrementAndGet(); }
    }
};

c = counter.init();
ArrayEach([1,2,3,4,5,6,7,8,9,10], c, true, 4);
writeOutput(c.n.get()); // 10
Real-world pipelines with Java SAMs
Vendor-feed cleanup — chain BIF + Java SAM + closure
upperizer = java {
    public class UpperFn implements java.util.function.Function {
        public UpperFn() {}
        public Object apply(Object value) { return value.toString().toUpperCase(); }
    }
};

validSku = java {
    public class ValidSku implements java.util.function.Predicate {
        public ValidSku() {}
        public boolean test(Object value) {
            String s = value.toString();
            return s.length() == 7 && s.startsWith("SKU-");
        }
    }
};

rawFeed = ["  sku-001 ", "bad", "sku-002", " SKU-003", "   ", "sku-004"];
clean = rawFeed.map(trim)
              .map(upperizer.init())
              .filter(validSku.init())
              .map((s) => "PROD:" & s);
writeOutput(arrayToList(clean));
// PROD:SKU-001,PROD:SKU-002,PROD:SKU-003,PROD:SKU-004
Order rollup — QueryFilter + QueryReduce with two Java SAMs
q = queryNew("id,month,amount", "integer,varchar,double",
    [[1,"2026-05",250.00],[2,"2026-04",180.00],[3,"2026-05",420.50],[4,"2026-05",75.00]]);

isThisMonth = java {
    public class CurrentMonth implements java.util.function.Predicate {
        public CurrentMonth() {}
        public boolean test(Object row) {
            return ((java.util.Map) row).get("MONTH").toString().equals("2026-05");
        }
    }
};

sumAmounts = java {
    public class SumAmt implements java.util.function.BiFunction {
        public SumAmt() {}
        public Object apply(Object acc, Object row) {
            double a = ((java.lang.Number) acc).doubleValue();
            double v = ((java.lang.Number) ((java.util.Map) row).get("AMOUNT")).doubleValue();
            return java.lang.Double.valueOf(a + v);
        }
    }
};

monthOrders = QueryFilter(q, isThisMonth.init());
total       = QueryReduce(monthOrders, sumAmounts.init(), 0);
writeOutput("Orders this month: " & monthOrders.recordCount & ", total: " & total);
// Orders this month: 3, total: 745.5
Parallel batch processor — StructEach(parallel=true) + BiConsumer + AtomicInteger
jobs = StructNew("ordered");
for (i = 1; i <= 100; i++) jobs["job_" & i] = "PENDING";

processor = java {
    public class JobProcessor implements java.util.function.BiConsumer {
        public java.util.concurrent.atomic.AtomicInteger processed
            = new java.util.concurrent.atomic.AtomicInteger(0);
        public JobProcessor() {}
        public void accept(Object key, Object value) {
            processed.incrementAndGet();
        }
    }
};

worker = processor.init();
StructEach(jobs, worker, true, 10);
writeOutput("Processed: " & worker.processed.get()); // 100
Config validator — StructReduce + BiFunction collecting failures
config = {
    port:    { value: 8080,        rule: "positive" },
    host:    { value: "localhost", rule: "nonempty" },
    threads: { value: -2,          rule: "positive" },
    retries: { value: "abc",       rule: "positive" }
};

failureCollector = java {
    public class FailColl implements java.util.function.BiFunction {
        public FailColl() {}
        public Object apply(Object acc, Object entry) {
            java.util.List failures = (java.util.List) acc;
            java.util.Map.Entry e = (java.util.Map.Entry) entry;
            java.util.Map cfg = (java.util.Map) e.getValue();
            String rule = cfg.get("RULE").toString();
            Object v    = cfg.get("VALUE");
            boolean ok = false;
            if (rule.equals("positive"))
                ok = (v instanceof java.lang.Number) && ((java.lang.Number) v).doubleValue() > 0;
            else if (rule.equals("nonempty"))
                ok = (v != null) && !v.toString().isEmpty();
            if (!ok) failures.add(e.getKey().toString());
            return failures;
        }
    }
};

failures = StructReduce(config,
                        failureCollector.init(),
                        createObject("java", "java.util.ArrayList").init());
writeOutput("Invalid keys: " & failures.toString());
// Invalid keys: [THREADS, RETRIES]
Matching the right Java SAM to each ColdFusion BIF callback shape.
BIFCallback shapeMatching Java SAM
arrayMap, listMapone input → one outputjava.util.function.Function (apply)
arrayFilter, listFilter, arrayEvery, arraySomeone input → booleanjava.util.function.Predicate (test)
arrayEach, listEachone input → no returnjava.util.function.Consumer (accept)
arrayReduce, listReduce(accumulator, item) → accumulatorjava.util.function.BiFunction (apply)
arraySort(a, b) → intjava.util.Comparator (compare)

Use ColdFusion callbacks where Java APIs expect a SAM

For the inverse direction — passing a Java SAM instance into a ColdFusion BIF — see Java SAM instances as ColdFusion callbacks.
What is a SAM type?
A SAM interface is an interface with one abstract method (excluding default or static methods on the interface).
Common Java SAM types and their typical use in ColdFusion interoperability.
Java typeSingle abstract methodTypical use
java.lang.Runnablevoid run()Run a task on a thread
java.util.concurrent.Callable<V>V call()Return a value from a background task
java.util.function.Predicate<T>boolean test(T t)Yes/no test
java.util.function.Function<T,R>R apply(T t)Transform T to R
java.util.function.Consumer<T>void accept(T t)Side effect per item
java.util.function.Supplier<T>T get()Produce a value (lazy)
Java lambda expressions and method references target SAM types. ColdFusion does not compile to Java lambda bytecode; instead, the runtime often adapts a CF function object to the SAM when you pass it into a Java method.
Why this matters for ColdFusion developers
  • Reuse Java libraries (AWS SDK, JDBC helpers, HTTP clients, validation, CSV parsers) that take Predicate, Function, or Consumer instead of writing everything in CFML.
  • Executor frameworks expect Runnable / Callable for background work — closures map naturally.
  • Streams (collection.stream().filter(...).map(...)) are SAM-heavy; CF closures are the readable bridge.
  • Consistent mental model: one CF function → one Java abstract method when arity and types line up.
Pass a ColdFusion closure where Java expects a SAM
Collection.removeIf(Predicate) removes elements that match the predicate.
ArrayList = createObject("java", "java.util.ArrayList");
list = ArrayList.init();
list.add("keep");
list.add("");
list.add("also");

list.removeIf(function (s) {
    return len(trim(s)) == 0;
});
writeOutput(list.size()); // 2
Runnable and Callable — threads and executors
// ExecutorService.submit(Runnable)
Executors = createObject("java", "java.util.concurrent.Executors");
executor = Executors.newSingleThreadExecutor();
future = executor.submit(function () {
    writeLog(file="application", text="Runnable from CF");
});
future.get();
executor.shutdown();
// Callable — return a value
Executors = createObject("java", "java.util.concurrent.Executors");
executor = Executors.newFixedThreadPool(2);
future = executor.submit(function () {
    return year(now());
});
result = future.get();
writeOutput(result);
executor.shutdown();
Predicate, Function, Consumer, Supplier
// Predicate — filter in memory before CFML arrayFilter
ArrayList = createObject("java", "java.util.ArrayList");
raw = ArrayList.init();
raw.add("001");
raw.add("XX");
raw.add("002");
valid = raw.stream().filter(function (code) {
    return isNumeric(code) && len(code) == 3;
});
Collectors = createObject("java", "java.util.stream.Collectors");
filteredList = valid.collect(Collectors.toList());
writeOutput(filteredList.size()); // 2
// Function — map strings to uppercase on the Java stream
ArrayList = createObject("java", "java.util.ArrayList");
raw = ArrayList.init();
raw.add("a");
raw.add("b");
mapped = raw.stream().map(function (s) {
    return ucase(s);
});
Collectors = createObject("java", "java.util.stream.Collectors");
out = mapped.collect(Collectors.toList());
writeOutput(out.toString()); // [A, B]
// Consumer — forEach side effects
lines = createObject("java", "java.util.ArrayList").init();
lines.add("a");
lines.add("b");
lines.forEach(function (line) {
    writeLog(file="application", text=line);
});
// Supplier — lazy initialization (cache pattern)
supplier = function () {
    return createObject("java", "java.util.Date").init();
};
// pass supplier to a Java factory that calls .get() when needed
BinaryOperator, BiFunction, and reducers
ArrayList = createObject("java", "java.util.ArrayList");
nums = ArrayList.init();
nums.add(3);
nums.add(7);
nums.add(2);
Stream = nums.stream();
optionalMax = Stream.reduce(function (a, b) {
    return max(a, b);
});
writeOutput(optionalMax.orElse(0)); // 7
Streams — java.util.stream
ArrayList = createObject("java", "java.util.ArrayList");
list = ArrayList.init();
list.add("10.5");
list.add("x");
list.add("3.2");
parsed = list.stream()
    .filter(function (s) {
        return isNumeric(s);
    })
    .map(function (s) {
        return javacast("double", s);
    });
Collectors = createObject("java", "java.util.stream.Collectors");
outList = parsed.collect(Collectors.toList());
writeOutput(outList.size()); // 2
Real-world scenarios
Background logging and metrics (Runnable) — queue non-critical audit rows on a worker thread:
executor = createObject("java",
    "java.util.concurrent.Executors").newFixedThreadPool(4);
executor.submit(function () {
    queryExecute(
        "INSERT INTO audit_log (msg) VALUES (:m)",
        { m: { value: "processed", cfsqltype: "cf_sql_varchar" } },
        { datasource: application.dsn }
    );
});
Sanitize IDs before SQL IN list construction:
rawIds = createObject("java", "java.util.ArrayList").init();
rawIds.add("1001");
rawIds.add("bad");
rawIds.add("1002");
clean = rawIds.stream()
    .filter(function (id) {
        return isNumeric(id) && len(id) <= 10;
    })
    .collect(createObject("java", "java.util.stream.Collectors").toList());
writeOutput(clean.size()); // 2
Third-party JAR: Optional.orElseGet(Supplier):
Optional = createObject("java", "java.util.Optional");
opt = Optional.empty();
value = opt.orElseGet(function () {
    return "default-from-cf";
});
writeOutput(value); // default-from-cf

Passing Java SAM instances as callbacks to ColdFusion BIFs — stateful examples

CF closures and BIF callbacks are the right default for most array, list, and string operations. They are concise and require no Java interop. Reach for a Java SAM instance as the callback when one or more of these is true:
  • The callback is expensive to construct (loads a model file, opens a connection, compiles a regex, builds a lookup set) and you want to pay that cost once before iterating, not once per item.
  • The callback is stateful. It holds fields that persist across invocations (caches, counters, model handles, prepared statements).
  • The logic already exists in a Java library you want to reuse without rewriting in CFML.
Pattern: use the inline java{} block (CF 2025.0.08), or a compiled .class / JAR on the CF classpath, to define a class that implements a SAM interface. The most common is java.util.function.Function (one input, one output), which matches arrayMap's callback shape. Instantiate the class from CFML using .init(...), passing any constructor arguments needed to set up state. Pass the instance as the callback argument to a CF BIF; ColdFusion calls the SAM's single abstract method (apply, test, accept, etc.) once per element.
Example 1 — Stateful sentiment analyzer used as arrayMap callback
A Function<Map, Map> implementation tokenizes text and tags parts of speech, holding the tokenizer and POS tagger as instance fields so the models load once in the constructor and are reused for every review.
sentimentAnalyzerClass = java{
    import opennlp.tools.tokenize.TokenizerME;
    import opennlp.tools.tokenize.TokenizerModel;
    import opennlp.tools.postag.POSTaggerME;
    import opennlp.tools.postag.POSModel;
    import java.io.FileInputStream;
    import java.util.Arrays;
    public class CustomerReviewAnalyzer implements java.util.function.Function {
        private TokenizerME tokenizer;
        private POSTaggerME posTagger;
        private java.util.Set<String> positiveWords;
        private java.util.Set<String> negativeWords;
        public CustomerReviewAnalyzer(String tokenizerPath, String posModelPath) {
            try {
                FileInputStream tokenizerStream = new FileInputStream(tokenizerPath);
                tokenizer = new TokenizerME(new TokenizerModel(tokenizerStream));
                tokenizerStream.close();
                FileInputStream posStream = new FileInputStream(posModelPath);
                posTagger = new POSTaggerME(new POSModel(posStream));
                posStream.close();
                positiveWords = new java.util.HashSet<>(Arrays.asList(
                    "good","great","excellent","amazing","love","best","awesome",
                    "recommend","happy","satisfied","pleased","outstanding"
                ));
                negativeWords = new java.util.HashSet<>(Arrays.asList(
                    "bad","terrible","horrible","awful","poor","worst","hate",
                    "disappointed","waste","useless","broke","defective"
                ));
            } catch(Exception e) {
                throw new RuntimeException("Failed to initialize analyzer: " + e.getMessage());
            }
        }
        @Override
        public Object apply(Object obj) {
            java.util.Map review = (java.util.Map) obj;
            String text = review.get("text").toString().toLowerCase();
            String[] tokens = tokenizer.tokenize(text);
            int positiveScore = 0, negativeScore = 0;
            for (String t : tokens) {
                if (positiveWords.contains(t)) positiveScore++;
                if (negativeWords.contains(t)) negativeScore++;
            }
            String sentiment = positiveScore > negativeScore ? "Positive"
                : negativeScore > positiveScore ? "Negative"
                : "Neutral";
            java.util.Map result = new java.util.HashMap();
            result.put("reviewId", review.get("reviewId"));
            result.put("sentiment", sentiment);
            result.put("positiveScore", positiveScore);
            result.put("negativeScore", negativeScore);
            return result;
        }
    }
};

// Build the analyzer ONCE — model files load once, not per review.
analyzer = sentimentAnalyzerClass.init(
    expandPath("./models/opennlp/en-token.bin"),
    expandPath("./models/opennlp/en-pos-maxent.bin")
);

reviews = [
    { reviewId: 1, text: "Absolutely amazing laptop, best purchase this year!" },
    { reviewId: 2, text: "Terrible product. Broke in two days. Complete waste." },
    { reviewId: 3, text: "It is okay. Nothing special." }
];

analyzed = arrayMap(reviews, analyzer);
writeDump(analyzed);
Example 2 — Named-entity recognizer as a reusable arrayMap callback
A Function implementation that wraps three Apache OpenNLP NameFinderME models (person, location, organization). Each text in the input array goes through the same loaded models; nothing is re-initialized per item.
nerProcessorClass = java{
    import opennlp.tools.tokenize.TokenizerME;
    import opennlp.tools.tokenize.TokenizerModel;
    import opennlp.tools.namefind.NameFinderME;
    import opennlp.tools.namefind.TokenNameFinderModel;
    import opennlp.tools.util.Span;
    import java.io.FileInputStream;
    public class NamedEntityRecognizer implements java.util.function.Function {
        private TokenizerME tokenizer;
        private NameFinderME personFinder;
        private NameFinderME locationFinder;
        private NameFinderME organizationFinder;
        public NamedEntityRecognizer(String tokenizerPath, String personModelPath,
                String locationModelPath, String orgModelPath) {
            try {
                tokenizer = new TokenizerME(new TokenizerModel(new FileInputStream(tokenizerPath)));
                personFinder = new NameFinderME(new TokenNameFinderModel(new FileInputStream(personModelPath)));
                locationFinder = new NameFinderME(new TokenNameFinderModel(new FileInputStream(locationModelPath)));
                organizationFinder = new NameFinderME(new TokenNameFinderModel(new FileInputStream(orgModelPath)));
            } catch(Exception e) {
                throw new RuntimeException("Failed to initialize NER: " + e.getMessage());
            }
        }
        @Override
        public Object apply(Object obj) {
            String text = obj.toString();
            String[] tokens = tokenizer.tokenize(text);
            java.util.Map result = new java.util.HashMap();
            result.put("text", text);
            result.put("persons", extract(tokens, personFinder));
            result.put("locations", extract(tokens, locationFinder));
            result.put("organizations", extract(tokens, organizationFinder));
            return result;
        }
        private java.util.List<String> extract(String[] tokens, NameFinderME finder) {
            java.util.List<String> out = new java.util.ArrayList<>();
            for (Span span : finder.find(tokens)) {
                StringBuilder sb = new StringBuilder();
                for (int i = span.getStart(); i < span.getEnd(); i++) {
                    sb.append(tokens[i]).append(" ");
                }
                out.add(sb.toString().trim());
            }
            finder.clearAdaptiveData();
            return out;
        }
    }
};

nerProcessor = nerProcessorClass.init(
    expandPath("./models/opennlp/en-token.bin"),
    expandPath("./models/opennlp/en-ner-person.bin"),
    expandPath("./models/opennlp/en-ner-location.bin"),
    expandPath("./models/opennlp/en-ner-organization.bin")
);

texts = [
    "Apple Inc. CEO Tim Cook announced the new iPhone in San Francisco, California.",
    "Microsoft and Oracle are competing for cloud market share in Europe and Asia.",
    "John Smith from IBM will meet Sarah Johnson from Amazon in New York next week."
];

nerResults = arrayMap(texts, nerProcessor);
writeDump(nerResults);

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