Whatever message this page gives is out now! Go check it out!
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.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.a.map(ucase) — you could get a runtime error such as:Cannot cast coldfusion.runtime.CFPageMethod to coldfusion.runtime.UDFMethoda.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.ArrayMap(arr, ucase) or arr.filter(isNumeric).fn = ucase; arr.map(fn).function parameter, the same as a UDF.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.<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.| Benefit | What you get |
|---|---|
| Less noise | Write a.map(ucase) instead of a.map(function (s) { return ucase(s); }) wherever the logic is identical. |
| Easier pipelines | Chains such as raw.map(trim).filter(isNumeric) read clearly without nested function blocks. |
| Familiar style | Other JVM and functional languages routinely pass library functions as callbacks; ColdFusion aligns with that pattern for BIFs. |
| Pluggable behavior | Store [ucase, lcase, trim] or pass isNumeric into a generic helper without redundant wrappers. |
| Predictable refactoring | Where supported, BIFs and equivalent UDFs yield the same results — easier code review and fewer subtle differences. |
(key, value) or (row) function.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);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]allNums = ArrayEvery(["123", "456", "789"], isNumeric);
writeOutput(allNums); // truehasNum = ArraySome(["abc", "123", "def"], isNumeric);
writeOutput(hasNum); // trueArrayEach(["a", "b"], ucase);
writeOutput("done"); // "done" (ArrayEach is void, no return)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); // 80fns = [ucase, lcase, trim];
idx = ArrayFind(fns, lcase);
writeOutput(idx); // 2ArraySort 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,Cherrycsv = "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|456out = ListMap("hello,world", ucase);
writeOutput(out); // HELLO,WORLDwriteOutput(ListEvery("123,456,789", isNumeric)); // true
writeOutput(ListSome("abc,123,def", isNumeric)); // trueListEach("a,b,c", ucase);
writeOutput("done"); // "done" (ListEach is void, no return)nums = "3,7,2,9,5";
writeOutput(ListReduce(nums, max, 0)); // 9
// Semicolon delimiter
writeOutput(ListReduce("3;7;2;9;5", max, 0, ";")); // 9max or min are typically ASCII (or Unicode) integer codes, not one-character strings.digits = "35729";
n = StringReduce(digits, max, 0);
writeOutput(n); // 57 — ASCII code of '9', not the number 9digits = "35729";
digitMax = StringReduce(digits, function (prev, code) {
ch = chr(code);
d = val(ch);
return max(prev, d);
}, 0);
writeOutput(digitMax); // 9<cffunction> page methods because the runtime cannot infer whether the callback should run on keys or values. Two callable shapes are accepted:(key, value) for filter/each/every/some/map; (acc, key, value) for reduce.s = { "1": "one", "abc": "two", "3": "three" };
filtered = StructFilter(s, function (key, value) {
return isNumeric(key);
});
writeOutput(structCount(filtered)); // 2s = { "a": "hello", "b": "world" };
out = StructMap(s, function (key, value) {
return ucase(value);
});
writeDump(out); // { "a": "HELLO", "b": "WORLD" }s = { "3": "a", "7": "b", "2": "c" };
maxKey = StructReduce(s, function (acc, key, value) {
return max(acc, val(key));
}, 0);
writeOutput(maxKey); // 7<cffunction> page methods because the runtime cannot infer which column the BIF should target. Accepted shapes:Predicate / Function / Consumer / BiFunction) QuerySort accepts only CF closures and UDFs in this release; java.util.Comparator is not yet wired up.q = queryNew("name,age", "varchar,integer", [
["John", 30], ["Jane", 25], ["Bob", 35]
]);
young = QueryFilter(q, function (row) {
return row.age < 32;
});
writeOutput(young.recordCount); // 2q = queryNew("score", "integer", [[90], [85], [95]]);
top = QueryReduce(q, function (prev, row) {
return max(prev, row.score);
}, 0);
writeOutput(top); // 95myFunc = ucase;
a = ["hello", "world"];
writeDump(a.map(myFunc)); // ["HELLO","WORLD"]transformers = [ucase, lcase, reverse];
input = "Hello";
results = [];
for (fn in transformers) {
arrayAppend(results, fn(input));
}
writeOutput(arrayToList(results)); // HELLO,hello,olleHops["upper"]("x") may not parse; retrieve into a variable first.ops = { upper: ucase, lower: lcase, rev: reverse };
fn = ops["upper"];
writeOutput(fn("hello")); // HELLOfunction 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"]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| Callback shape | Java SAM | Abstract method |
|---|---|---|
(value) -> R | java.util.function.Function | Object apply(Object) |
(value) -> boolean | java.util.function.Predicate | boolean test(Object) |
(value) -> void | java.util.function.Consumer | void accept(Object) |
(a, b) -> R | java.util.function.BiFunction | Object apply(Object, Object) |
(a, b) -> boolean | java.util.function.BiPredicate | boolean test(Object, Object) |
(a, b) -> void | java.util.function.BiConsumer | void accept(Object, Object) |
(a, a) -> a | java.util.function.BinaryOperator | Object apply(Object, Object) |
// 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// 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); // 4java.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<cffunction> page methods. Java SAMs are accepted because the SAM signature is explicit.| Struct operation | SAM | Signature |
|---|---|---|
StructFilter, StructEvery, StructSome | BiPredicate | boolean test(key, value) |
StructEach | BiConsumer | void accept(key, value) |
StructMap | BiFunction | Object apply(key, value) |
StructReduce | BiFunction | Object apply(acc, java.util.Map.Entry) — call getKey() / getValue() inside the SAM |
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); // 15java.util.Map; column names are upper-cased keys. As with Struct, bare CF BIFs and <cffunction> are rejected; Java SAMs are accepted.| Query operation | SAM | Signature |
|---|---|---|
QueryFilter, QueryEvery, QuerySome | Predicate | boolean test(row) |
QueryEach | Consumer | void accept(row) |
QueryMap | Function | Object apply(row) |
QueryReduce | BiFunction | Object apply(acc, row) |
QuerySort | CF closure / UDF only | java.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)); // 105ArrayEach/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.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()); // 10upperizer = 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-004q = 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.5jobs = 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()); // 100config = {
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]| BIF | Callback shape | Matching Java SAM |
|---|---|---|
arrayMap, listMap | one input → one output | java.util.function.Function (apply) |
arrayFilter, listFilter, arrayEvery, arraySome | one input → boolean | java.util.function.Predicate (test) |
arrayEach, listEach | one input → no return | java.util.function.Consumer (accept) |
arrayReduce, listReduce | (accumulator, item) → accumulator | java.util.function.BiFunction (apply) |
arraySort | (a, b) → int | java.util.Comparator (compare) |
| Java type | Single abstract method | Typical use |
|---|---|---|
java.lang.Runnable | void 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) |
Predicate, Function, or Consumer instead of writing everything in CFML.Runnable / Callable for background work — closures map naturally.collection.stream().filter(...).map(...)) are SAM-heavy; CF closures are the readable bridge.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// 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 — 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 neededArrayList = 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)); // 7ArrayList = 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()); // 2executor = 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 }
);
});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()); // 2Optional = createObject("java", "java.util.Optional");
opt = Optional.empty();
value = opt.orElseGet(function () {
return "default-from-cf";
});
writeOutput(value); // default-from-cfjava{} 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.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);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);