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.