Introducing Quarkus quickjs4j: Seamless JavaScript Integration in Your Quarkus Applications

Introduction

We’re excited to announce the release of the Quarkus quickjs4j extension, a powerful new addition to the Quarkus ecosystem that enables seamless execution of JavaScript code within your Java applications. Built on top of the quickjs4j library, this extension brings the lightweight QuickJS JavaScript engine to both JVM and Native Quarkus, with full CDI integration and compile-time optimizations.

Whether you need to execute dynamic business logic, implement configurable rules engines, or integrate with JavaScript-based algorithms, the Quarkus quickjs4j extension provides a type-safe, performant solution that leverages Quarkus’s build-time processing capabilities.

Why JavaScript in Java Applications?

Quarkus applications often need to execute dynamic logic that can be modified without recompiling the entire application. JavaScript provides an excellent solution for this use case, offering:

  • Dynamic Configuration: Update business rules and logic without application restarts

  • Scripting Capabilities: Enable power users to customize application behavior

  • Algorithm Integration: Leverage existing JavaScript libraries and algorithms

  • Rapid Prototyping: Quickly test and iterate on complex logic

The Quarkus quickjs4j extension makes this integration seamless while maintaining the performance and developer experience you expect from Quarkus.

Key Features

Compile-time Code Generation

The extension automatically generates CDI beans and proxy classes for your JavaScript interfaces during build time, ensuring optimal performance and early error detection.

Full CDI Integration

JavaScript interfaces are first-class citizens in your Quarkus application, injectable like any other CDI bean.

Flexible Script Loading

Load JavaScript files from multiple sources: - Classpath resources (recommended for packaged scripts) - Filesystem paths (for dynamic script loading) - URLs (for remote script execution) - Anywhere else (using the optional Factory pattern)

Context Support

Pass Java objects as context to JavaScript execution, enabling bidirectional communication between Java and JavaScript code.

Sandboxed Execution

QuickJs4J provides a secure and efficient way to execute JavaScript within Java. By running code in a sandbox, it ensures:

  • Memory safety – JavaScript runs in isolation, protecting your application from crashes or memory leaks.

  • No system access by default – JavaScript cannot access the filesystem, network, or other sensitive resources unless explicitly allowed.

  • Portability – Being pure Java bytecode, it runs wherever the JVM does.

  • Native-image friendly – Compatible with GraalVM’s native-image for fast, lightweight deployments.

Whether you’re embedding scripting capabilities or isolating untrusted code, QuickJs4J is designed for safe and seamless integration.

Getting Started

Adding the extension to your Quarkus application is straightforward. First, add the dependency to your pom.xml:

<dependency>
    <groupId>io.quarkiverse.quickjs4j</groupId>
    <artifactId>quarkus-quickjs4j</artifactId>
    <version>${quarkus-quickjs4j.version}</version>
</dependency>

You’ll also need to enable the annotation processor for code generation:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>io.quarkiverse.quickjs4j</groupId>
                        <artifactId>quarkus-quickjs4j</artifactId>
                        <version>${quarkus-quickjs4j.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

Simple Example: JavaScript Calculator

Let’s create a simple calculator to demonstrate the extension’s capabilities.

First, define a Java interface annotated with @ScriptInterface and @ScriptImplementation:

package com.example;

import io.roastedroot.quickjs4j.annotations.ScriptInterface;
import io.quarkiverse.quickjs4j.annotations.ScriptImplementation;

@ScriptInterface
@ScriptImplementation(location = "calculator.js")
public interface Calculator {
    int add(int a, int b);
    int multiply(int a, int b);
}

Next, create the JavaScript implementation in src/main/resources/calculator.js:

function add(a, b) {
    return a + b;
}

function multiply(a, b) {
    return a * b;
}

export { add, multiply };

Finally, inject and use the calculator in your Quarkus application:

package com.example;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped
public class MathService {

    @Inject
    Calculator calculator;

    public int performCalculation() {
        int sum = calculator.add(5, 3);        // Returns 8
        int product = calculator.multiply(4, 7); // Returns 28
        double quotient = calculator.divide(10.0, 2.0); // Returns 5.0

        return sum + product + (int) quotient;
    }
}

That’s it! The extension handles all the complexity of JavaScript execution, type conversion, and CDI integration behind the scenes.

Advanced Features

Context Objects for Bidirectional Communication

A powerful feature of quickjs4j is the ability to provide Java context objects that JavaScript code can invoke:

@ScriptInterface(context = CalculatorContext.class)
@ScriptImplementation(location = "calculator.js")
public interface Calculator {
    int add(int a, int b);
    int multiply(int a, int b);
}
@ApplicationScoped
public class CalculatorContext {
    public void log(String message) {
        System.out.println("Calc>> " + message);
    }
}

Your JavaScript code can then call these Java methods:

function add(a, b) {
    Calculator_Builtins.log(`Adding ${a} + ${b}`);
    return a + b;
}

function multiply(a, b) {
    Calculator_Builtins.log(`Multiplying ${a} * ${b}`);
    return a * b;
}

export { add, multiply };

Factory Pattern for Dynamic Scripts

For scenarios where you need to load scripts dynamically at runtime, use the factory pattern:

@ApplicationScoped
public class DynamicMathService {

    @Inject
    CalculatorContext context;

    @Inject
    ScriptInterfaceFactory<Calculator, CalculatorContext> calculatorFactory;

    public void executeCustomScript() {
        // Load your javascript from some dynamic source
        String scriptContent = loadDynamicScriptContent();

        // Create calculator instance with dynamic script
        Calculator calculator = calculatorFactory.create(scriptContent, context);

        // Use the calculator
        int result = calculator.add(10, 20);
        System.out.println("Result: " + result);
    }
}

This approach is perfect for applications that need to execute user-provided scripts or load scripts from external sources. Note that the execution of the script is fully sandboxed. Only the methods exposed by the Context can be invoked from within the script.

Error Handling and Debugging

JavaScript errors are propagated as Java exceptions, making debugging straightforward:

try {
    double result = calculator.divide(10, 0);
} catch (RuntimeException e) {
    logger.error("JavaScript execution failed: {}", e.getMessage(), e);
    // Handle the error appropriately
}

Build-time Magic

Behind the scenes, the extension performs build-time code generation, creating:

  1. CDI Bean Classes: {InterfaceName}_CDI - Injectable CDI beans

  2. Factory Classes: {InterfaceName}_Factory - Injectable factory beans

  3. Proxy Classes: {InterfaceName}_Proxy - Generated by quickjs4j

  4. Context Builtins: {ContextName}_Builtins - JavaScript-accessible Java methods

This build-time approach ensures minimal runtime overhead while providing full IDE support with code completion and type checking.

Performance Considerations

The QuickJS engine is designed for lightweight, fast JavaScript execution. Combined with Quarkus’s build-time optimizations, the extension provides:

  • Fast Startup: Minimal impact on application startup time

  • Low Memory Footprint: Efficient memory usage for JavaScript execution

  • Native Image Support: Full compatibility with GraalVM native images

  • Build-time Validation: Early detection of interface mismatches and errors

Use Cases

The Quarkus quickjs4j extension is perfect for:

  • Business Rules Engines: Implement configurable business logic

  • Template Processing: Generate dynamic content with JavaScript templates

  • Algorithm Integration: Leverage existing JavaScript algorithms and libraries

  • User Scripting: Allow power users to customize application behavior

  • Configuration Logic: Implement complex configuration scenarios

Current Status and Future Plans

The extension is currently in experimental status, meaning APIs may evolve based on community feedback. We’re actively working on:

  • Enhanced error reporting and debugging capabilities

  • Performance optimizations

  • Configurable JavaScript engine options

  • Improved IDE integration and tooling

In particular, we have a lot of work to do in optimizing performance. There are clear tradeoffs to consider around flexibility and speed, as well as customization. The current experimental implementation takes a very conservative approach to ensure full sandboxing and thread safety. The result is a slower implementation, but one that is guaranteed to be thread safe and fully sandboxed.

Getting Involved

The Quarkus quickjs4j extension is part of the Quarkiverse ecosystem and welcomes community contributions. Whether you’re interested in:

  • Reporting bugs or requesting features

  • Contributing code improvements

  • Sharing use cases and examples

  • Improving documentation

Visit our GitHub repository to get involved. We would really love for you to try out quickjs4j in Quarkus and give us feedback. The best way to evolve the functionality is by hearing from users!

Conclusion

The Quarkus quickjs4j extension opens up exciting possibilities for Java developers who need to integrate JavaScript execution into their applications. With its compile-time code generation, full CDI integration, and flexible script loading options, it provides a powerful yet easy-to-use solution for dynamic code execution.

Try it out and let us know what you think! We’re excited to see what the community builds with this capability.

If you want to learn more about QuickJS itself, or the upstream quickjs4j Java project, here are some helpful links: