QuickJS
Since Camel 4.23
The QuickJS language evaluates JavaScript as an Expression or Predicate in Camel routes.
camel-javascript provides GraalVM JavaScript with Java interoperability, while camel-quickjs provides a lightweight pure-Java JavaScript runtime using QuickJS4J and JSON-based data exchange.
QuickJS4J compiles QuickJS to WebAssembly and runs it as Java bytecode through Endive (the successor to Chicory). There is no JNI and no native library. Native-image compatibility and architecture-specific support have not been validated as part of this module.
Do not treat this language as a drop-in replacement for JavaScript. The scripting APIs and Exchange bindings are different.
For example, you can use QuickJS in a Predicate with the Content-Based Router EIP.
Variables
The following variables are bound for each evaluation. Values are JSON snapshots taken before the script runs, not live Java objects. Live Exchange, Message and CamelContext instances are never exposed; the camel API below is the way to read the current state or to change it.
| Variable | Type | Description |
|---|---|---|
body | JSON value | the message body after JSON conversion |
headers | Object | the message headers after JSON conversion |
properties | Object | the exchange properties after JSON conversion |
exchangeId | String | the exchange id |
variables | Object | the exchange variables after JSON conversion (an empty object when there are none) |
exception | Object or null |
|
camel | Object | the controlled Camel API, see below |
message, exchange, and context are not bound. Scripts that refer to them raise a JavaScript ReferenceError.
Assigning to headers, properties, variables or body inside a script changes only the JavaScript snapshot. It does not mutate the Camel Exchange. Use the camel API, or the expression result (for example .transform().quickjs(…)) when you need to change the message.
The camel API
camel is a frozen object whose functions read and write the current exchange through QuickJS4J host functions. Arguments and results cross the boundary as JSON, so a value written with camel.setHeader is stored as a String, Number, Boolean, List or Map.
| Function | Description |
|---|---|
| the current message body (JSON snapshot; a streaming body raises the same error as the |
| replaces the message body |
| a header of the current message (JSON snapshot) |
| sets a header on the current message |
| removes a header and returns its previous value |
| the same for exchange properties |
| the same for exchange variables |
| logs through the |
from("direct:start")
.setBody().quickjs("camel.setHeader('processed', true); body.toUpperCase()")
.to("mock:result"); The camel API is only available while a route expression is evaluated; the generic ScriptingLanguage.evaluate(script, bindings, resultType) entry point has no current exchange.
Expressions and statements
A script that is a single expression (body.amount > 100, { a: body }) is compiled as an expression and returns its value. Any other script (several statements, a trailing semicolon, a var declaration) is evaluated as statements and returns its completion value, the value of the last statement, exactly as eval would. Note that { a: 1 } is therefore an object when it is the whole script but a block statement when it is followed by other statements.
The generic ScriptingLanguage.evaluate(script, bindings, resultType) API uses caller-supplied map keys as JavaScript function parameters. Those keys must be valid JavaScript identifiers (for example body or foo_bar). Names such as foo-bar, 123foo, or reserved words such as for are rejected with a Camel evaluation exception rather than a raw JavaScript SyntaxError. Route expressions do not use this map.
Data types
Values cross the Java/JavaScript boundary as JSON:
-
null, string, boolean, and number pass through. -
Mapbecomes a JavaScript object. Header and property names are strings, soheaders.MyHeaderandheaders['MyHeader']both work. -
Listand arrays become JavaScript arrays. -
byte[]becomes a Base64 JSON string.char[]becomes a JSON string. -
Other Java types are serialized with Jackson into a JSON object or array snapshot. Java methods such as
getAge()are not callable from JavaScript; use JSON fields such asbody.age. -
Exchange,Message,CamelContext,Class, andClassLoadervalues are rejected when they appear as the message body, with an evaluation error. They are never passed into the script. -
Streaming bodies (
InputStream,Reader, and CamelStreamCache) are rejected with an evaluation error. The stream is not read, closed, or otherwise consumed. -
Header and property values that cannot be JSON-serialized (including Camel internals and streaming values) are omitted from the JavaScript snapshot so evaluation can still use the remaining data.
Serialization failures of the message body raise a Camel evaluation exception that names the unsupported type.
Expression and predicate
As an expression, the JavaScript value of the script becomes the Camel result (then converted with Camel type converters when a result type is requested).
As a predicate (.when().quickjs(…) or .filter().quickjs(…)), the result is converted to boolean with Camel’s standard ObjectHelper.evaluateValuePredicate rules: a Boolean is used directly; the strings true/false are parsed; any other non-empty, non-null value is true.
Engine lifecycle
Every worker thread owns one QuickJS engine, created on first use and closed when the language stops. Each engine keeps the last 1,000 route expressions it evaluated in compiled form, so a script is compiled once per thread and then only executed. A JavaScript exception thrown by a script leaves the engine usable; a trap inside the runtime (a camel function that failed, a stack overflow) does not, and the engine of that thread is then discarded and recreated on the next evaluation. QuickJS keeps every module it has evaluated until its context is freed, and QuickJS4J evaluates a module per call, so an engine grows with every evaluation. The language therefore recycles a thread’s engine once its WebAssembly memory exceeds engineMaxMemory (64 MB) or it has run engineMaxEvaluations (50,000) evaluations. Both are properties of QuickjsLanguage and can be set like any language option, for example in application.properties:
camel.language.quickjs.engineMaxMemory = 134217728
camel.language.quickjs.engineMaxEvaluations = 100000 or programmatically through QuickjsLanguage) context.resolveLanguage("quickjs".setEngineMaxMemory(…).
Security
JavaScript runs in the QuickJS4J sandbox. The runtime does not expose Java classes, reflection, class loaders, or live Camel objects. WASI has no filesystem or network preopens. Stdout from scripts is discarded so a reused engine does not accumulate output. Per-evaluation stderr is captured into Camel exceptions (for example ReferenceError) and then cleared so later evaluations do not include stale error output.
QuickJS4J host plumbing is not available to user scripts: java_invoke throws a TypeError and quickjs4j_engine is undefined, including when accessed through globalThis. The camel API is the only host bridge scripts can reach, and it only dispatches to the functions listed above.
Usage
import static org.apache.camel.language.quickjs.QuickjsLanguage.quickjs;
public class MyRouteBuilder extends RouteBuilder {
@Override
public void configure() {
from("direct:start")
.choice()
.when().quickjs("headers.MyHeader == 'foo'").to("mock:foo")
.otherwise().to("mock:other");
}
} Transform the body with the expression result:
from("direct:start")
.transform().quickjs("body.toUpperCase()")
.to("mock:result"); You can load the script from an external resource with the resource:scheme:location syntax, for example resource:classpath:myscript.js or resource:file:/path/to/script.js.
| Do not derive |
Dependencies
To use QuickJS in your Camel routes, you need to add the dependency on camel-quickjs.
QuickJS4J is licensed under Apache License 2.0 (ASF Category A).
If you use Maven, you could add the following to your pom.xml, substituting the version number for the latest release.
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-quickjs</artifactId>
<version>x.x.x</version>
</dependency>