The departure of Nashorn from the JDK left a void for Java developers needing to execute JavaScript within their applications. GraalJS, part of the revolutionary GraalVM project, doesn't just fill this void—it completely redefines what's possible by providing a high-performance, ECMAScript-compliant JavaScript engine that supports the entire modern JavaScript language spectrum, all while offering unprecedented polyglot capabilities.
What is GraalJS?
GraalJS is an implementation of JavaScript built on the GraalVM stack. It's not just another embedded engine; it's a fundamental part of GraalVM's vision for a polyglot runtime. Key characteristics:
- Built on Truffle: Utilizes the Truffle Language Implementation Framework, which allows for powerful AST interpretation and optimization.
- JIT-Optimized: Leverages the Graal compiler to achieve performance that often rivals or surpasses Node.js for pure JavaScript execution.
- Fully Modern: Implements ECMAScript 2023 and beyond, with support for features like async/await, classes, modules, and more.
- Polyglot by Design: Can interoperate seamlessly with other Truffle languages (Python, R, Ruby, LLVM) within the same runtime.
Architecture: Why GraalJS is Different
The power of GraalJS comes from its unique architecture within the GraalVM ecosystem, which enables unprecedented performance and interoperability:
graph TB subgraph "GraalVM Runtime" A[Java Application] B[GraalJS Engine] C[Graal Compiler] D[Polyglot API] subgraph "Truffle Languages" E[JavaScript] F[Python] G[R] H[Ruby] I[Java] end A --> D B --> E D --> E D --> F D --> G D --> H D --> I E --> C F --> C G --> C end style E fill:#f9f,stroke:#333,stroke-width:2px style C fill:#ccf,stroke:#333,stroke-width:2px
Getting Started: Basic Integration
1. Dependencies
For GraalVM users, GraalJS is included. For other JDKs, add the dependency:
<!-- Maven --> <dependency> <groupId>org.graalvm.polyglot</groupId> <artifactId>polyglot</artifactId> <version>23.1.0</version> </dependency> <dependency> <groupId>org.graalvm.polyglot</groupId> <artifactId>js</artifactId> <version>23.1.0</version> <type>pom</type> </dependency>
2. Basic JavaScript Execution
import org.graalvm.polyglot.*;
public class BasicGraalJS {
public static void main(String[] args) {
try (Context context = Context.create()) {
// Execute simple JavaScript
Value result = context.eval("js",
"const message = 'Hello from JavaScript';" +
"message.toUpperCase();"
);
System.out.println(result.asString()); // HELLO FROM JAVASCRIPT
// Evaluate expressions with return values
Value sum = context.eval("js", "2 + 3 * 4");
System.out.println("Result: " + sum.asInt()); // Result: 14
// Work with JavaScript objects
Value jsObject = context.eval("js",
"({ name: 'Alice', age: 30, hobbies: ['reading', 'coding'] })"
);
System.out.println("Name: " + jsObject.getMember("name").asString());
System.out.println("First hobby: " +
jsObject.getMember("hobbies").getArrayElement(0).asString());
}
}
}
Advanced JavaScript Features
1. Modern ES2023+ Features
try (Context context = Context.create()) {
// ES6+ features fully supported
String modernJS = """
// Array spreading and destructuring
const parts = [1, 2];
const numbers = [...parts, 3, 4];
const [first, ...rest] = numbers;
// Arrow functions and template literals
const greet = (name) => `Hello, ${name}!`;
// Classes and modules
class Person {
constructor(name) { this.name = name; }
speak() { return `${this.name} is speaking`; }
}
// Async/Await
const fetchData = async () => {
return new Promise(resolve =>
setTimeout(() => resolve('Data loaded'), 100)
);
};
// Return multiple values
{ first: first, rest: rest, greeting: greet('Alice'),
person: new Person('Bob'), fetchData: fetchData }
""";
Value result = context.eval("js", modernJS);
System.out.println("First: " + result.getMember("first").asInt());
System.out.println("Rest length: " + result.getMember("rest").getArraySize());
System.out.println("Greeting: " + result.getMember("greeting").asString());
System.out.println("Person: " + result.getMember("person").invokeMember("speak").asString());
// Execute async function
Value promise = result.getMember("fetchData").execute();
promise.await(); // Wait for promise resolution
System.out.println("Async result: " + promise.asString());
}
2. Java and JavaScript Interoperability
The true power emerges when Java and JavaScript call each other seamlessly:
try (Context context = Context.create()) {
// Expose Java objects to JavaScript
context.getBindings("js").putMember("javaMath", Math.class);
context.getBindings("js").putMember("javaList", Arrays.asList("A", "B", "C"));
// JavaScript can call Java methods
String jsCode = """
// Call static Java method
const random = javaMath.random();
// Call instance methods on Java objects
const size = javaList.size();
const firstElement = javaList.get(0);
// Create Java arrays
const javaArray = Java.type('int[]');
const array = new javaArray(3);
array[0] = 42;
{ random: random, size: size, first: firstElement, array: array }
""";
Value result = context.eval("js", jsCode);
System.out.println("Random from Java: " + result.getMember("random").asDouble());
System.out.println("List size from Java: " + result.getMember("size").asInt());
// Call JavaScript functions from Java
Value jsFunction = context.eval("js",
"(function(x, y) { return x * y + javaMath.sqrt(x); })"
);
Value functionResult = jsFunction.execute(16, 3);
System.out.println("JavaScript function result: " + functionResult.asDouble());
}
Real-World Use Cases
1. Rules Engine with JavaScript
public class JavaScriptRulesEngine {
private final Context context;
public JavaScriptRulesEngine() {
this.context = Context.create();
// Pre-load utility functions
context.eval("js", """
function evaluateDiscount(customer, orderTotal) {
if (customer.status === 'GOLD' && orderTotal > 1000) {
return orderTotal * 0.15; // 15% discount
} else if (customer.status === 'SILVER' && orderTotal > 500) {
return orderTotal * 0.10; // 10% discount
} else if (orderTotal > 2000) {
return orderTotal * 0.05; // 5% bulk discount
}
return 0;
}
function validateOrder(order) {
const errors = [];
if (!order.items || order.items.length === 0) {
errors.push('Order must contain items');
}
if (order.total < 0) {
errors.push('Total cannot be negative');
}
return errors;
}
""");
}
public double calculateDiscount(Map<String, Object> customer, double orderTotal) {
context.getBindings("js").putMember("customer", customer);
context.getBindings("js").putMember("orderTotal", orderTotal);
Value discount = context.eval("js", "evaluateDiscount(customer, orderTotal)");
return discount.asDouble();
}
public List<String> validateOrder(Map<String, Object> order) {
context.getBindings("js").putMember("order", order);
Value errors = context.eval("js", "validateOrder(order)");
return errors.as(List.class);
}
}
2. Template Processing
public class JavaScriptTemplateEngine {
private final Context context;
public JavaScriptTemplateEngine() {
this.context = Context.create();
// Load template processing library (conceptual)
context.eval("js", """
function renderTemplate(template, data) {
return template.replace(/\\$\\{([^}]+)\\}/g, (match, key) => {
return data[key] !== undefined ? data[key] : match;
});
}
function processArray(items, template) {
return items.map(item => renderTemplate(template, item)).join('');
}
""");
}
public String render(String template, Map<String, Object> data) {
context.getBindings("js").putMember("template", template);
context.getBindings("js").putMember("data", data);
Value result = context.eval("js", "renderTemplate(template, data)");
return result.asString();
}
}
// Usage
JavaScriptTemplateEngine engine = new JavaScriptTemplateEngine();
String template = "Hello, ${name}! Your order ${orderId} is ${status}.";
Map<String, Object> data = Map.of(
"name", "Alice",
"orderId", "ORD-12345",
"status", "shipped"
);
String result = engine.render(template, data);
// Hello, Alice! Your order ORD-12345 is shipped.
Performance Considerations and Best Practices
1. Context Reuse
// ✅ Good: Reuse context (expensive to create)
public class JavaScriptService {
private final Context context;
public JavaScriptService() {
this.context = Context.newBuilder("js")
.allowAllAccess(true) // Be careful with this in production!
.build();
}
public void close() {
context.close();
}
}
// ❌ Bad: Creating context for every execution
public void inefficientCall() {
try (Context context = Context.create()) { // Expensive!
context.eval("js", "someFunction()");
}
}
2. Value Caching and Type Handling
try (Context context = Context.create()) {
// Cache frequently used JavaScript functions
Value cachedFunction = context.eval("js",
"(function(x) { return x * x + Math.sqrt(x); })"
);
// Reuse the same function instance
for (int i = 0; i < 1000; i++) {
double result = cachedFunction.execute(i).asDouble();
// Process result
}
// Efficient type conversion
Value jsArray = context.eval("js", "[1, 2, 3, 4, 5]");
// Convert to Java array efficiently
int[] javaArray = jsArray.as(int[].class);
// Or work with it as Value for better performance
int sum = 0;
for (int i = 0; i < jsArray.getArraySize(); i++) {
sum += jsArray.getArrayElement(i).asInt();
}
}
Comparison with Alternatives
| Feature | GraalJS | Nashorn | Rhino |
|---|---|---|---|
| ECMAScript Support | ES2023+ | ES5.1 | ES5 |
| Performance | Excellent (JIT) | Good | Poor |
| Java Interop | Seamless | Good | Limited |
| Polyglot | Yes | No | No |
| Maintenance | Active | Deprecated | Maintenance |
Conclusion
GraalJS represents a quantum leap for JavaScript execution on the JVM. It's not merely a replacement for Nashorn but a fundamentally more powerful technology that:
- Supports the entire modern JavaScript ecosystem with full ES2023+ compliance
- Delivers exceptional performance through GraalVM's advanced JIT compilation
- Enables true polyglot applications that can seamlessly blend JavaScript, Java, Python, and more
- Provides rich interoperability that makes Java/JavaScript integration feel natural
For Java applications that need rules engines, template processing, plugin systems, or any form of dynamic scripting, GraalJS offers a future-proof solution that combines the flexibility of JavaScript with the robustness of the JVM ecosystem. It transforms JavaScript from being merely "embedded" to being a fully integrated, high-performance component of your Java applications.