Close

Java Compiler API - Compile Code to a File and Load it using Default Class Loader

[Last Updated: Jan 23, 2016]

In the previous topic we saw how to compile java sources from files. In this example we are going to show how to compile a string object containing java code. Here are the steps:

  1. Extend SimpleJavaFileObject, let's say as class JavaStringObject
  2. Override getCharContent(..) in JavaStringObject to return our in memory string object instead of reading some file content from disk.
    public class JavaStringObject extends SimpleJavaFileObject {
        private final String source;
    
        protected JavaStringObject(String name, String source) {
            super(URI.create("string:///" + name.replaceAll("\\.", "/") +
                    Kind.SOURCE.extension), Kind.SOURCE);
            this.source = source;
        }
    
        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors)
                                                     throws IOException {
            return source;
        }
    }
  3. Pass the instance of JavaStringObject (bundled in Iterable) as the last argument to JavaCompiler#getTask(..).
  4. We are also going to skip standardFileManager.getJavaFileObjects(files) call, as it returns instance of the JavaFileObject (On debugging, I found the default implementation comes as com.sun.tools.javac.file.RegularFileObject) which handles reading source content from a file.
    public class StringCompilation {
    
        public static void main(String[] args) {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    
            DiagnosticCollector<JavaFileObject> diagnostics =
                                             new DiagnosticCollector<>();
    
            JavaCompiler.CompilationTask task = compiler.getTask(null,
                    null, diagnostics, null, null, getCompilationUnits());
    
            if (!task.call()) {
                diagnostics.getDiagnostics().forEach(System.out::println);
            }
        }
    
        public static Iterable<? extends JavaFileObject> getCompilationUnits() {
            JavaStringObject stringObject =
                                       new JavaStringObject("Test", getSource());
            return Arrays.asList(stringObject);
        }
    
        public static String getSource() {
            return "public class Test{" +
                    "public void doSomething(){" +
                    "System.out.println(\"testing\");}}";
        }
    }
    

Loading the Compiled Source

Classes compiled this way can only be loaded (via reflection) if they are already in the classpath. The compiled file, in our example, goes to the project root if we are running it from an IDE. I'm using Intellij:

That means we have to set the root of the project as classpath before running the main method. There's currently no standard way to set classpath during runtime.

Example Project

Here's the complete example project. Notice I implemented a predefined interface ITest to our generated class Test so that we won't be doing everything (method calling) via reflection.

Dependencies and Technologies Used:

  • JDK 1.8
  • Maven 3.0.4

Compiler Api String Source Example Select All Download
  • compiling-string-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • StringCompilation.java

    Running from Jar

    Running the application from jar (I packaged it using: mvn package) will compile the class file Test.class next to the jar file.

    D:\compiling-string-example\target>dir
     Volume in drive D is Data
     Volume Serial Number is 68F9-EDFA
     
    Directory of D:\compiling-string-example\target
     
    12/27/2015  05:04 PM    <DIR>          .
    12/27/2015  05:04 PM    <DIR>          ..
    12/27/2015  04:24 PM    <DIR>          classes
    12/27/2015  05:01 PM             9,700 compiling-string-example-1.0-SNAPSHOT.jar
    12/27/2015  04:24 PM    <DIR>          generated-sources
    12/27/2015  04:24 PM    <DIR>          maven-archiver
    12/27/2015  05:01 PM    <DIR>          surefire
    12/27/2015  04:08 PM               421 Test.class
                   2 File(s)         10,121 bytes
                   6 Dir(s)  84,648,378,368 bytes free

    That means we can run it like this:

    D:\compiling-string-example\target>java -cp
        .;compiling-string-example-1.0-SNAPSHOT.jar
        com.logicbig.example.StringCompilation
    testing
    

    Also be careful about a JRE 1.7 and JRE 1.8 bug. If you have a null pointer exception on the compiler instance (ToolProvider.getSystemJavaCompiler() can return null), you have to set JDK path instead of JRE. JRE 6.x works fine.


    Setting an Explicit Output Path

    We can control the class output folder by using fileManager.setLocation.

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    
    DiagnosticCollector<JavaFileObject> diagnostics =
                                     new DiagnosticCollector<>();
    
    StandardJavaFileManager fileManager =
            compiler.getStandardFileManager(diagnostics, null, null);
    
    //should be platform independent path
    //also the folder should already exist
    fileManager.setLocation(StandardLocation.CLASS_OUTPUT,
            Arrays.asList(new File("d:\\compiledClasses")));
    
    JavaCompiler.CompilationTask task = compiler.getTask(null,
            fileManager, diagnostics, null, null, getCompilationUnits());
    
    if (!task.call()) {
        diagnostics.getDiagnostics().forEach(System.out::println);
    }
    fileManager.close();
    
    //loading and using our compiled class
    Class<ITest> test = (Class<ITest>) Class.forName("Test");
    ITest iTest = test.newInstance();
    iTest.doSomething();

    But of course it doesn't change the running class path. It will just compile the files in that folder. We still have to set our runtime path before running the app.

    D:\compiling-string-example\target>java -cp
        .;D:\compiledClasses;compiling-string-example-1.0-SNAPSHOT.jar
        com.logicbig.example.StringCompilation
    testing
    

    See Also