javascriptjavagraalvmnashorn

Is there any way to execute simple JavaScript expressions in Java 21 without any manual installations?


I have version 21 installed and as far as I understand, I can't use Nashorn engine anymore, but maybe GraalVM is available for me?

I have installed all the necessary dependencies:

<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js</artifactId>
    <version>21.1.0</version>
</dependency>
<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js-scriptengine</artifactId>
    <version>21.1.0</version>
</dependency>

Here is a simple example that I'm trying to execute:

public static void main(String[] args) throws ScriptException {
    try (Context context = Context.newBuilder("js")
            .allowAllAccess(true)
            .build()) {
        // Evaluate JavaScript code
        String jsCode = "console.log('Hello, GraalVM!');";
        context.eval("js", jsCode);
    } catch (Exception e) {
        throw new ScriptException("Script execution failed: " + e.getMessage());
    }
}

However, I get an error:

Exception in thread "main" javax.script.ScriptException: Script execution failed: A language with id 'js' is not installed. Installed languages are: []. at org.example.Main.main(Main.java:23)

I also tried something like this:

public static void main(String[] args) throws ScriptException {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine  = manager.getEngineByName("JavaScript");
    engine.eval("print('HI');");
}

But I got another error:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "javax.script.ScriptEngine.eval(String)" because "engine" is null at org.example.Main.main(Main.java:20)

The problem is that manual installation of any components is impossible for some reason. I just need to some dependencies and make everything works. Something "out of the box". Is there any workaround for this problem? Maybe there are any other available engines?


Solution

  • Graalvm js - Project

    jsEngineScriptJDK21_graalvm_js

    ├── pom.xml
    └── src
        └── main
            └── java
                └── com
                    └── example
                        └── JsTest.java
    
    

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.example</groupId>
        <artifactId>graalvm-js-engine-script</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>GraalvmJsEngineScript</name>
        <description>jsEngineScript App Project</description>
        <properties>
            <maven.compiler.source>21</maven.compiler.source>
            <maven.compiler.target>21</maven.compiler.target>
            <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        </properties>
    
        <dependencies>
    
        <dependency>
            <groupId>org.graalvm.js</groupId>
            <artifactId>js</artifactId>
            <version>23.0.0</version>
        </dependency>
            
        </dependencies>    
    
        <build>
            <finalName>app</finalName>
        </build>
    
    </project>
    

    JsTest.java

    package com.example;
    
    import org.graalvm.polyglot.Context;
    import org.graalvm.polyglot.Value;
    
    public class JsTest {
    
      public static void main(String[] args) throws Exception {
      
        try (Context context = Context.newBuilder("js")
                .allowAllAccess(true)
                .build()) {
            // Evaluate JavaScript code
            String jsCode = "console.log('Hello, GraalVM!');";
            context.eval("js", jsCode);
        } catch (Exception e) {
            throw new Exception("Script execution failed: " + e.getMessage());
        }
        
      }
      
    }
    

    JsTest3.java

    package com.example;
    
    import org.graalvm.polyglot.Context;
    import org.graalvm.polyglot.Value;
    
    public class JsTest3 {
    
        public static void main(String[] args) throws Exception {
    
            try (Context context = Context.newBuilder("js")
                    .option("engine.WarnInterpreterOnly", "false")
                    .allowAllAccess(true)
                    .build()) {
    
                Value result = context.eval("js", "2 + 2");
                System.out.println("Result: " + result.asInt());
    
                // Call JavaScript Function
                context.eval("js", "function greet(name) { return 'Hello, ' + name; }");
                Value greetFunction = context.getBindings("js").getMember("greet");
                System.out.println("Greeting: " + greetFunction.execute("World").asString());
    
    
                // Evaluate JavaScript code
                String jsCode = "print('HI');";
                context.eval("js", jsCode);
    
            } catch (Exception e) {
                throw new Exception("Script execution failed: " + e.getMessage());
            }
    
        }
    
    }
    
    

    Build And Run

    Build

    mvn clean package
    
    mvn dependency:copy-dependencies -DoutputDirectory=target/libs
    

    Run

    JsTest

    java -cp "target/libs/*:target/app.jar" \
      com.example.JsTest
    

    Result:

    [To redirect Truffle log output to a file use one of the following options:
    * '--log.file=<path>' if the option is passed using a guest language launcher.
    * '-Dpolyglot.log.file=<path>' if the option is passed using the host Java launcher.
    * Configure logging using the polyglot embedding API.]
    [engine] WARNING: The polyglot context is using an implementation that does not support runtime compilation.
    The guest application code will therefore be executed in interpreted mode only.
    Execution only in interpreted mode will strongly impact the guest application performance.
    For more information on using GraalVM see https://www.graalvm.org/java/quickstart/.
    To disable this warning the '--engine.WarnInterpreterOnly=false' option or use the '-Dpolyglot.engine.WarnInterpreterOnly=false' system property.
    Hello, GraalVM!
    

    JsTest3

    java -cp "target/libs/*:target/app.jar" \
      com.example.JsTest3
    

    Result: There is no warning message.

    Result: 4
    Greeting: Hello, World
    HI
    

    JDK Version

    $ javac -version
    javac 21.0.4
    
    $ java -version
    openjdk version "21.0.4" 2024-07-16 LTS
    OpenJDK Runtime Environment Temurin-21.0.4+7 (build 21.0.4+7-LTS)
    OpenJDK 64-Bit Server VM Temurin-21.0.4+7 (build 21.0.4+7-LTS, mixed mode, sharing)
    

    Notice:

    nashorn script engine - Project

    jsEngineScriptJDK21

    ├── pom.xml
    └── src
        └── main
            └── java
                └── com
                    └── example
                        └── JsTest.java
    

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.example</groupId>
        <artifactId>js-engine-script</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>jsEngineScript</name>
        <description>jsEngineScript App Project</description>
        <properties>
            <maven.compiler.source>21</maven.compiler.source>
            <maven.compiler.target>21</maven.compiler.target>
            <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.openjdk.nashorn</groupId>
                <artifactId>nashorn-core</artifactId>
                <version>15.4</version>
                <scope>runtime</scope>
            </dependency>
        </dependencies>
    
        <build>
            <finalName>app</finalName>
        </build>
    
    </project>
    

    JsTest.java

    package com.example;
    
    import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;
    import javax.script.ScriptException;
    
    public class JsTest {
        public static void main(String[] args) throws ScriptException {
            try {
                //ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
                //ScriptEngine engine  =new ScriptEngineManager().getEngineByName("JavaScript");
                ScriptEngineManager manager = new ScriptEngineManager();
                ScriptEngine engine  = manager.getEngineByName("JavaScript");
    
                if (engine == null) {
                    System.out.println("Nashorn script engine not available.");
                    return;
                }
    
                engine.eval("print('HI');");
                engine.eval("print('Hello, Nashorn!');");
                engine.eval("var x = 10 + 20; print('x = ' + x);");
    
            } catch (ScriptException e) {
                e.printStackTrace();
            }
        }
    }
    

    Build And Run

    Build

    mvn clean package
    
    mvn dependency:copy-dependencies -DoutputDirectory=target/libs
    

    Run

    java -cp "target/libs/*:target/app.jar" \
      com.example.JsTest
    

    Result:

    HI
    Hello, Nashorn!
    x = 30