# QuickJS

**Since Camel 4.23**

The QuickJS language evaluates JavaScript as an [Expression](../../../manual/expression.md) or [Predicate](../../../manual/predicate.md) 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](js-language.md). The scripting APIs and Exchange bindings are different.

For example, you can use QuickJS in a [Predicate](../../../manual/predicate.md) with the [Content-Based Router](../eips/choice-eip.md) EIP.

## QuickJS Options

The QuickJS language supports the following options which are listed below.

   
| Name | Default | Java Type | Description |
| --- | --- | --- | --- |
| **resultType** (common) |  | `String` | The class of the result type (type from output). |
| **trim** (advanced) | `true` | `Boolean` | Whether to trim the source code to remove leading and trailing whitespaces and line breaks. |
| **resolveResource** (advanced) | `false` | `Boolean` | Whether a result of the expression that is a String starting with resource: is loaded as a resource and its content becomes the result, e.g. a script that returns resource:file:order.json or resource:classpath:templates/order.json (a name without a scheme is a classpath resource). Off by default; the resource: prefix on the expression text itself is always resolved. Applies to the expression used as a value, not as a predicate. |

## 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 | `null`, or `{ type, message }` of the exception on the exchange (or the caught exception in an error handler) |
| 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 |
| --- | --- |
| `camel.getBody()` | the current message body (JSON snapshot; a streaming body raises the same error as the `body` binding) |
| `camel.setBody(value)` | replaces the message body |
| `camel.getHeader(name)` | a header of the current message (JSON snapshot) |
| `camel.setHeader(name, value)` | sets a header on the current message |
| `camel.removeHeader(name)` | removes a header and returns its previous value |
| `camel.getProperty(name)` / `camel.setProperty(name, value)` / `camel.removeProperty(name)` | the same for exchange properties |
| `camel.getVariable(name)` / `camel.setVariable(name, value)` / `camel.removeVariable(name)` | the same for exchange variables |
| `camel.log(level, message)` | logs through the `org.apache.camel.language.quickjs.script` logger at `trace`, `debug`, `info`, `warn` or `error` |

-   Java
    
-   XML
    
-   YAML
    

```java
from("direct:start")
    .setBody().quickjs("camel.setHeader('processed', true); body.toUpperCase()")
    .to("mock:result");
```

```xml
<route>
  <from uri="direct:start"/>
  <setBody>
    <quickjs>camel.setHeader('processed', true); body.toUpperCase()</quickjs>
  </setBody>
  <to uri="mock:result"/>
</route>
```

```yaml
- route:
    from:
      uri: direct:start
      steps:
        - setBody:
            expression:
              quickjs:
                expression: 'camel.setHeader(''processed'', true); body.toUpperCase()'
        - to:
            uri: 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.
    
-   `Map` becomes a JavaScript object. Header and property names are strings, so `headers.MyHeader` and `headers['MyHeader']` both work.
    
-   `List` and 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 as `body.age`.
    
-   `Exchange`, `Message`, `CamelContext`, `Class`, and `ClassLoader` values 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 Camel `StreamCache`) 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`:

```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

-   Java
    
-   XML
    
-   YAML
    

```java
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");
    }
}
```

```xml
<route>
  <from uri="direct:start"/>
  <choice>
    <when>
      <quickjs>headers.MyHeader == 'foo'</quickjs>
      <to uri="mock:foo"/>
    </when>
    <otherwise>
      <to uri="mock:other"/>
    </otherwise>
  </choice>
</route>
```

```yaml
- route:
    from:
      uri: direct:start
      steps:
        - choice:
            when:
              - expression:
                  quickjs:
                    expression: 'headers.MyHeader == ''foo'''
                steps:
                  - to:
                      uri: mock:foo
            otherwise:
              steps:
                - to:
                    uri: mock:other
```

Transform the body with the expression result:

-   Java
    
-   XML
    
-   YAML
    

```java
from("direct:start")
    .transform().quickjs("body.toUpperCase()")
    .to("mock:result");
```

```xml
<route>
  <from uri="direct:start"/>
  <transform>
    <quickjs>body.toUpperCase()</quickjs>
  </transform>
  <to uri="mock:result"/>
</route>
```

```yaml
- route:
    from:
      uri: direct:start
      steps:
        - transform:
            expression:
              quickjs:
                expression: body.toUpperCase()
        - to:
            uri: 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`.

> **Warning**
> Do not derive `resource:classpath:` or `resource:file:` paths from untrusted input such as message headers or query parameters. A path taken from untrusted data can cause Camel to load and evaluate an unexpected JavaScript file.

## 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.

```xml
<dependency>
  <groupId>org.apache.camel</groupId>
  <artifactId>camel-quickjs</artifactId>
  <version>x.x.x</version>
</dependency>
```