5

I'm migrating an application to Java 10. Our application normally runs with the JRE, but we allow users to compile bits of their own custom code by bundling tools.jar and using reflection to load a JavacTool instance on demand. Our method looks like:

public static JavaCompiler getJavaCompiler() {
    String toolJarName = "tools.jar";
    File file = getResourceForClass("tools.jar");
    try {
        file = file.getCanonicalFile();
    } catch (IOException ignore) {
    }
    if (!file.exists())
        throw new RuntimeException("Can't find java tools file: '"+file+"'");
    try {
        URL[] urls = new URL[]{ file.toURI().toURL() };
        URLClassLoader cl = URLClassLoader.newInstance(urls);
        return Class.forName("com.sun.tools.javac.api.JavacTool", false, cl).asSubclass(JavaCompiler.class).newInstance();
    } catch (Exception e) {
        throw new RuntimeException("Can't find java compiler from '"+file+"': "+e.getMessage());
    }
}

This is necessary because javax.tools.ToolProvider.getSystemJavaCompiler() returns null when running from the JRE. Our method worked well with Java 8, but tools.jar was removed in Java 9, and the class I need com.sun.tools.javac.api.JavacTool is in the jdk.compiler module which remains part of the JDK, but not the JRE.

Is there any way to load the jdk.compiler module when launching the JRE? I suspect it's not, based on this answer, and my attempts to use --add-module result in: java.lang.module.FindException: JMOD format not supported at execution time

Is there another solution I'm missing?

rainbowgoblin
  • 1,131
  • 10
  • 23
  • 2
    How about trying to create a [custom runtime image using `jlink`](https://docs.oracle.com/javase/9/tools/jlink.htm#JSWOR-GUID-CECAC52B-CFEE-46CB-8166-F17A8E9280E9) and `jmod`? Also, thought of [using the `ToolProvider`](https://github.com/namannigam/jdk9-tool/blob/master/src/com/JavaCTool.java) for your use case? – Naman May 31 '18 at 18:33
  • @nullpointer Just curious: Does the `java.util.spi.ToolProvider` in your GitHub sample work even if no JDK is present? I doubt that, which means that this probably won't help the OP. – M A May 31 '18 at 20:30
  • 1
    @manouti With my suggestion, I meant creating a custom run-time image and as for the implementation within that, using the `ToolProvider` instead of `tools.jar` – Naman Jun 01 '18 at 01:55
  • 2
    This is really strange question. If the application is running on say JRE 8 then the user needs to have JDK 8 on the system too. Maybe the code in the question isn't about the JRE but rather than tools.jar wasn't on the class path by default? If so then there is no need for this code in JDK 9 and newer as the java.compiler and jdk.compiler modules are resolved by default. – Alan Bateman Jun 01 '18 at 08:13
  • @Alan Bateman: `jdk.compiler` isn't available with the Oracle JRE. When running `java --list-modules` with Java 10 JRE you'll notice `jdk.compiler` is conspicuously absent. – rainbowgoblin Jun 01 '18 at 14:15
  • @nullpointer I'm not totally clear on the advantage of using `java.util.spi.ToolProvider` rather than, say, `javax.tools.ToolProvider.getSystemJavaCompiler()`, but a custom runtime image seems like it's the solution (and also way more straightforward than I expected). If you add this as an answer I'll accept it. – rainbowgoblin Jun 01 '18 at 14:37
  • 1
    The question is still very strange. If you are using JRE 8 then where did it find tools.jar? There must be a JDK on the system and how did you check that it's the same version of the JRE you are running on. If you really want to run on the JRE then I think you will have to create a new VM to run the compiler. – Alan Bateman Jun 01 '18 at 14:38
  • We bundle `tools.jar` as a resource. We actually bundle the JRE with our application, so we *could* ensure it's the same version, but interestingly enough we've been bundling `tools.jar` from 8u25 and JRE 8u162, unnoticed by all. It worked fine until we migrated to Java 10. – rainbowgoblin Jun 01 '18 at 14:45
  • 2
    The equivalent with JDK 9 and newer is to run `jlink` to create a run-time image that includes the jdk.compiler module. – Alan Bateman Jun 01 '18 at 15:35

1 Answers1

4

IMO the simplest solution to your problem is to keep it simple and require users to download the JDK instead of the JRE:

... but we allow users to compile bits of their own custom code

It should come to no surprise to the users to download a JDK since, well, they are using a JDK feature: compiling code.

If that's not possible, then you may want to try the jlink solution that @nullpointer suggests in the comments.

M A
  • 65,721
  • 13
  • 123
  • 159
  • When I say "their own code"... developers aren't our target customers, so most of the custom code snippets they're using are written by third parties (their own IT support, our product forums, etc.) In fact, we bundle the JRE, so replacing it with the JDK bloats our application, but it's definitely something we're considering as a worst case solution. – rainbowgoblin Jun 01 '18 at 14:42
  • 2
    With Java 11, this solution became really easy, as there is no JRE anymore. Those, who know how to create a smaller runtime environment, are expected to know how to include the compiler. – Holger Oct 04 '18 at 10:10