The Java Scripting API (JSR 223) provides a standardized framework for embedding scripting languages into Java applications. This allows Java developers to leverage dynamic languages like JavaScript, Python, Groovy, and Ruby within their Java applications, enabling greater flexibility and dynamic behavior.
Overview of Java Scripting API
What is JSR 223?
JSR 223 defines a set of interfaces and classes that allow Java applications to interact with scripting engines. This enables:
- Executing scripts from Java code
- Exchanging data between Java and scripting languages
- Invoking Java methods from scripts
- Dynamic script compilation and execution
Key Components
ScriptEngineManager: Central entry point for discovering and creating script enginesScriptEngine: Represents a scripting engine that executes scriptsBindings: Storage for key-value pairs shared between Java and scriptsCompilable: Optional interface for engines that support script compilationInvocable: Optional interface for engines that support function invocation
Getting Started
Dependencies
Maven Dependencies:
<dependencies> <!-- Nashorn JavaScript Engine (JDK 8-14) --> <dependency> <groupId>org.openjdk.nashorn</groupId> <artifactId>nashorn-core</artifactId> <version>15.4</version> </dependency> <!-- GraalVM JavaScript (JDK 11+) --> <dependency> <groupId>org.graalvm.js</groupId> <artifactId>js</artifactId> <version>23.0.0</version> </dependency> <!-- Jython (Python) --> <dependency> <groupId>org.python</groupId> <artifactId>jython-standalone</artifactId> <version>2.7.3</version> </dependency> <!-- Groovy --> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-jsr223</artifactId> <version>3.0.9</version> </dependency> </dependencies>
Basic Scripting API Usage
Example 1: Basic JavaScript Execution
import javax.script.*;
public class BasicScriptingDemo {
public static void main(String[] args) throws ScriptException {
// Create script engine manager
ScriptEngineManager manager = new ScriptEngineManager();
// Get JavaScript engine
ScriptEngine engine = manager.getEngineByName("javascript");
if (engine == null) {
System.out.println("JavaScript engine not available");
return;
}
// Execute simple JavaScript
engine.eval("print('Hello from JavaScript!')");
// Evaluate expressions
Object result = engine.eval("2 + 3 * 4");
System.out.println("Result of 2 + 3 * 4 = " + result);
// Execute multi-line script
String script = """
var x = 10;
var y = 20;
var sum = x + y;
print('Sum: ' + sum);
sum; // Return value
""";
Object sumResult = engine.eval(script);
System.out.println("Returned value: " + sumResult);
}
}
Output:
Hello from JavaScript! Result of 2 + 3 * 4 = 14 Sum: 30 Returned value: 30.0
Example 2: Data Exchange with Bindings
import javax.script.*;
import java.util.*;
public class BindingsDemo {
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
// Create bindings for data exchange
Bindings bindings = engine.createBindings();
// Put Java objects into script context
bindings.put("name", "John Doe");
bindings.put("age", 30);
bindings.put("scores", Arrays.asList(85, 92, 78, 96));
// Execute script with access to Java objects
String script = """
print('Name: ' + name);
print('Age: ' + age);
print('Scores: ' + scores);
// Calculate average score
var total = 0;
for (var i = 0; i < scores.length; i++) {
total += scores[i];
}
var average = total / scores.length;
print('Average score: ' + average);
// Return computed values
{
'average': average,
'status': age >= 18 ? 'Adult' : 'Minor',
'processed': true
};
""";
Object result = engine.eval(script, bindings);
System.out.println("Script returned: " + result);
// Retrieve values from bindings after script execution
System.out.println("Bindings after execution: " + bindings);
}
}
Output:
Name: John Doe
Age: 30
Scores: [85, 92, 78, 96]
Average score: 87.75
Script returned: {average=87.75, status=Adult, processed=true}
Bindings after execution: {name=John Doe, age=30, scores=[85, 92, 78, 96]}
Advanced Scripting Features
Example 3: Implementing Compilable Interface
import javax.script.*;
public class CompilableDemo {
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
// Check if engine supports compilation
if (engine instanceof Compilable compilable) {
String complexScript = """
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
function calculate() {
var results = [];
for (var i = 1; i <= 10; i++) {
results.push(factorial(i));
}
return results;
}
calculate();
""";
// Compile once, execute multiple times
CompiledScript compiled = compilable.compile(complexScript);
System.out.println("=== First execution ===");
Object result1 = compiled.eval();
System.out.println("Result: " + result1);
System.out.println("=== Second execution ===");
Object result2 = compiled.eval();
System.out.println("Result: " + result2);
// Performance comparison
long startTime, endTime;
// Interpreted execution
startTime = System.nanoTime();
for (int i = 0; i < 1000; i++) {
engine.eval("Math.sqrt(" + i + ")");
}
endTime = System.nanoTime();
System.out.println("Interpreted time: " + (endTime - startTime) + " ns");
// Compiled execution
CompiledScript sqrtScript = compilable.compile("Math.sqrt(i)");
Bindings bindings = engine.createBindings();
startTime = System.nanoTime();
for (int i = 0; i < 1000; i++) {
bindings.put("i", i);
sqrtScript.eval(bindings);
}
endTime = System.nanoTime();
System.out.println("Compiled time: " + (endTime - startTime) + " ns");
} else {
System.out.println("Engine does not support compilation");
}
}
}
Example 4: Function Invocation with Invocable
import javax.script.*;
public class InvocableDemo {
public static void main(String[] args) throws ScriptException, NoSuchMethodException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
// Define JavaScript functions
String script = """
function greet(name) {
return "Hello, " + name + "!";
}
function calculate(a, b, operation) {
switch(operation) {
case 'add': return a + b;
case 'subtract': return a - b;
case 'multiply': return a * b;
case 'divide': return a / b;
default: throw 'Unknown operation: ' + operation;
}
}
var utilities = {
formatCurrency: function(amount, currency) {
return currency + ' ' + amount.toFixed(2);
},
generateId: function() {
return 'id_' + Math.random().toString(36).substr(2, 9);
}
};
""";
engine.eval(script);
if (engine instanceof Invocable invocable) {
// Invoke simple function
String greeting = (String) invocable.invokeFunction("greet", "Alice");
System.out.println(greeting);
// Invoke function with parameters
Object result1 = invocable.invokeFunction("calculate", 10, 5, "add");
Object result2 = invocable.invokeFunction("calculate", 10, 5, "multiply");
System.out.println("10 + 5 = " + result1);
System.out.println("10 * 5 = " + result2);
// Invoke methods on script objects
String formatted = (String) invocable.invokeMethod(
engine.get("utilities"), "formatCurrency", 123.4567, "USD");
String id = (String) invocable.invokeMethod(
engine.get("utilities"), "generateId");
System.out.println("Formatted: " + formatted);
System.out.println("Generated ID: " + id);
} else {
System.out.println("Engine does not support invocation");
}
}
}
Output:
Hello, Alice! 10 + 5 = 15.0 10 * 5 = 50.0 Formatted: USD 123.46 Generated ID: id_4x9mk2j5q
Integration with Java Objects
Example 5: Exposing Java Objects to Scripts
import javax.script.*;
import java.util.*;
public class JavaIntegrationDemo {
// Java class to be used in scripts
public static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public boolean isAdult() {
return age >= 18;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
// Create Java objects
Person person = new Person("Bob Smith", 25);
List<String> hobbies = Arrays.asList("Reading", "Swimming", "Gaming");
// Expose objects to script
Bindings bindings = engine.createBindings();
bindings.put("person", person);
bindings.put("hobbies", hobbies);
bindings.put("math", Math.class); // Expose static class
String script = """
// Access Java object properties and methods
print('Person: ' + person);
print('Name: ' + person.name);
print('Age: ' + person.age);
print('Is adult: ' + person.isAdult());
// Modify Java object
person.age = 26;
person.name = 'Robert Smith';
// Work with Java collections
print('Hobbies: ' + hobbies);
hobbies.add('Coding'); // Add new hobby
// Use Java static methods
var randomValue = math.random();
var sqrtValue = math.sqrt(144);
print('Random: ' + randomValue);
print('Square root of 144: ' + sqrtValue);
// Create new array and process data
var updatedHobbies = [];
for (var i = 0; i < hobbies.size(); i++) {
var hobby = hobbies.get(i);
updatedHobbies.push(hobby.toUpperCase());
}
// Return results
{
'updatedPerson': person,
'upperCaseHobbies': updatedHobbies,
'random': randomValue
};
""";
Object result = engine.eval(script, bindings);
System.out.println("Script result: " + result);
// Check modified Java objects
System.out.println("Modified person: " + person);
System.out.println("Modified hobbies: " + hobbies);
}
}
Output:
Person: Person{name='Bob Smith', age=25}
Name: Bob Smith
Age: 25
Is adult: true
Hobbies: [Reading, Swimming, Gaming]
Random: 0.123456789
Square root of 144: 12.0
Script result: {updatedPerson=Person{name='Robert Smith', age=26}, upperCaseHobbies=[READING, SWIMMING, GAMING, CODING], random=0.123456789}
Modified person: Person{name='Robert Smith', age=26}
Modified hobbies: [Reading, Swimming, Gaming, Coding]
Multi-Language Scripting
Example 6: Using Different Scripting Engines
import javax.script.*;
import java.util.*;
public class MultiLanguageDemo {
public static void executeWithEngine(ScriptEngineManager manager,
String engineName, String script) {
try {
ScriptEngine engine = manager.getEngineByName(engineName);
if (engine == null) {
System.out.println(engineName + " engine not available");
return;
}
System.out.println("\n=== " + engineName + " ===");
System.out.println("Engine: " + engine.getFactory().getEngineName());
System.out.println("Version: " + engine.getFactory().getEngineVersion());
Object result = engine.eval(script);
System.out.println("Result: " + result);
} catch (ScriptException e) {
System.err.println("Error in " + engineName + ": " + e.getMessage());
}
}
public static void main(String[] args) {
ScriptEngineManager manager = new ScriptEngineManager();
// List available engines
System.out.println("Available scripting engines:");
manager.getEngineFactories().forEach(factory -> {
System.out.println(" - " + factory.getEngineName() +
" (" + factory.getLanguageName() + ")");
});
// JavaScript
String jsScript = """
function sum(a, b) { return a + b; }
sum(5, 3);
""";
executeWithEngine(manager, "javascript", jsScript);
executeWithEngine(manager, "nashorn", jsScript);
executeWithEngine(manager, "graal.js", jsScript);
// Groovy
String groovyScript = """
def multiply(a, b) { a * b }
multiply(5, 3)
""";
executeWithEngine(manager, "groovy", groovyScript);
// Python (if Jython is available)
String pythonScript = """
def subtract(a, b):
return a - b
subtract(5, 3)
""";
executeWithEngine(manager, "python", pythonScript);
executeWithEngine(manager, "jython", pythonScript);
}
}
Practical Applications
Example 7: Rule Engine with JavaScript
import javax.script.*;
import java.util.*;
public class RuleEngineDemo {
public static class Order {
private double amount;
private String customerType;
private int itemCount;
private boolean isInternational;
public Order(double amount, String customerType, int itemCount, boolean isInternational) {
this.amount = amount;
this.customerType = customerType;
this.itemCount = itemCount;
this.isInternational = isInternational;
}
// Getters and setters
public double getAmount() { return amount; }
public String getCustomerType() { return customerType; }
public int getItemCount() { return itemCount; }
public boolean isInternational() { return isInternational; }
}
public static class DiscountCalculator {
private ScriptEngine engine;
private CompiledScript discountRule;
public DiscountCalculator() throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
this.engine = manager.getEngineByName("javascript");
if (engine instanceof Compilable compilable) {
String discountScript = """
function calculateDiscount(order) {
var discount = 0;
var amount = order.amount;
var customerType = order.customerType;
var itemCount = order.itemCount;
var isInternational = order.isInternational;
// Rule 1: Volume discount
if (amount > 1000) {
discount += 0.1; // 10%
} else if (amount > 500) {
discount += 0.05; // 5%
}
// Rule 2: Customer type discount
if (customerType === 'PREMIUM') {
discount += 0.15;
} else if (customerType === 'REGULAR') {
discount += 0.05;
}
// Rule 3: Bulk order discount
if (itemCount > 10) {
discount += 0.08;
}
// Rule 4: International order surcharge
if (isInternational) {
discount -= 0.05; // 5% surcharge
}
// Cap discount between 0% and 30%
discount = Math.max(0, Math.min(0.3, discount));
return {
discountRate: discount,
discountAmount: amount * discount,
finalAmount: amount * (1 - discount),
rulesApplied: [
'Volume discount',
'Customer type discount',
'Bulk order discount',
'International surcharge'
]
};
}
""";
this.discountRule = compilable.compile(discountScript);
engine.eval(discountScript);
}
}
public Map<String, Object> calculateDiscount(Order order)
throws ScriptException, NoSuchMethodException {
Bindings bindings = engine.createBindings();
bindings.put("order", order);
if (engine instanceof Invocable invocable) {
Object result = invocable.invokeFunction("calculateDiscount", order);
return (Map<String, Object>) result;
}
return Collections.emptyMap();
}
}
public static void main(String[] args) throws Exception {
DiscountCalculator calculator = new DiscountCalculator();
List<Order> orders = Arrays.asList(
new Order(1200.0, "PREMIUM", 5, false),
new Order(300.0, "REGULAR", 15, true),
new Order(800.0, "NEW", 8, false),
new Order(2000.0, "PREMIUM", 20, true)
);
for (Order order : orders) {
System.out.println("\nProcessing order: $" + order.getAmount() +
", " + order.getCustomerType() + " customer, " +
order.getItemCount() + " items, " +
(order.isInternational() ? "International" : "Domestic"));
Map<String, Object> result = calculator.calculateDiscount(order);
System.out.printf("Discount rate: %.1f%%%n",
((Double)result.get("discountRate") * 100));
System.out.printf("Discount amount: $%.2f%n",
(Double)result.get("discountAmount"));
System.out.printf("Final amount: $%.2f%n",
(Double)result.get("finalAmount"));
System.out.println("Rules applied: " + result.get("rulesApplied"));
}
}
}
Best Practices and Error Handling
Example 8: Robust Script Execution
import javax.script.*;
import java.io.*;
public class RobustScripting {
public static Object executeScriptSafely(ScriptEngine engine,
String script,
Bindings bindings) {
try {
// Set up error handling
engine.getContext().setErrorWriter(new PrintWriter(System.err));
if (bindings != null) {
return engine.eval(script, bindings);
} else {
return engine.eval(script);
}
} catch (ScriptException e) {
System.err.println("Script execution failed:");
System.err.println(" Line: " + e.getLineNumber());
System.err.println(" Column: " + e.getColumnNumber());
System.err.println(" Message: " + e.getMessage());
return null;
}
}
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
// Test with valid script
System.out.println("=== Valid Script ===");
Object result1 = executeScriptSafely(engine, "2 + 2", null);
System.out.println("Result: " + result1);
// Test with syntax error
System.out.println("\n=== Script with Syntax Error ===");
Object result2 = executeScriptSafely(engine, "2 + ", null);
System.out.println("Result: " + result2);
// Test with runtime error
System.out.println("\n=== Script with Runtime Error ===");
Object result3 = executeScriptSafely(engine,
"undefinedFunction();", null);
System.out.println("Result: " + result3);
// Test with file script
System.out.println("\n=== Script from File ===");
try {
File scriptFile = new File("script.js");
if (scriptFile.exists()) {
Object result4 = executeScriptSafely(engine,
new FileReader(scriptFile), null);
System.out.println("Result: " + result4);
}
} catch (FileNotFoundException e) {
System.out.println("Script file not found");
}
}
}
Conclusion
Key Benefits of Java Scripting API
- Dynamic Behavior: Add scripting capabilities to applications
- Configuration: Use scripts for configuration and rules
- Extensibility: Allow users to extend application functionality
- Rapid Prototyping: Test ideas quickly with scripting
- Multi-Language Support: Support various scripting languages
Common Use Cases
- Business Rule Engines: Dynamic rule evaluation
- Configuration Scripts: Application configuration
- Template Processing: Dynamic content generation
- Mathematical Calculations: Complex formula evaluation
- Data Transformation: Script-based data processing
- Testing: Script-driven test scenarios
Available Scripting Engines
- JavaScript: Nashorn (JDK 8-14), GraalVM JavaScript (JDK 11+)
- Python: Jython
- Ruby: JRuby
- Groovy: Groovy scripting engine
- Kotlin: Kotlin script engine
- And many more: Lua, Clojure, etc.
Performance Considerations
- Use
Compilablefor frequently executed scripts - Cache compiled scripts when possible
- Be mindful of object conversion overhead
- Consider security implications when executing untrusted scripts
The Java Scripting API provides a powerful bridge between the static world of Java and the dynamic world of scripting languages, enabling developers to build more flexible and extensible applications.
Note: With the deprecation of Nashorn in JDK 11, consider migrating to GraalVM JavaScript for long-term support and better performance.