Generating Java classes dynamically through Java compiler API

Some of the java coders around the world might have thought of an option to be able to compile a java source file dynamically.

To my surprise, almost at the end of Java 6 (I am expecting Java 7 to be out soon…), I noticed this feature under javax.tools package. May be I am the last one to notice this!! :-) .

This dynamic compiler API is included with Java 6 under javax.tools package.

How does it work?

javax.tools package has all the required interfaces and classes. Here, we will see how to compile a simple “HelloWorld” program source code stored in an in-memory String variable.

Able to compile a piece of source code stored in a string variable, WOW! this is interesting! isn’t it?

Follow the sequence of steps mentioned below. I explained these steps with the required code-snippets at that point. The full version of source code is available at the end of the article.

The most important classes in this API are,

  • JavaCompiler - This is used to create a compilation task
  • JavaCompiler.CompilationTask – The compilation task, on which we execute compile operation using it’s call method
  • JavaFileManager:Manages how the compiler read and writes to the files
  • JavaFileObject: The file object that abstracts the java source and class files
  • DiagnosticListener: This listens to the compilation diagnostic events
  • ToolProvider: Which is used to get the compiler object from the underlying platform.

We will discuss these classes further in the example below. Let’s start…

1. Build the source code to compile; we can read it from file system, retrieve from database, or generate it dynamically in memory!!

	/**Java source code to be compiled dynamically*/
	static String sourceCode = "package com.accordess.ca;" +
		"class DynamicCompilationHelloWorld{" +
			"public static void main (String args[]){" +
				"System.out.println (\"Hello, dynamic compilation world!\");" +
			"}" +
		"}" ;

2. Create a JavaFileObject instance for each of the compilation unit.

  • a) If the source is not from file system, then we need to write a class implementing from JavaFileObject interface. Java 6 provides a sample implementation of this in the form of SimpleJavaFileObject. We can extend from this and customize it as per our needs.
/**
 * Creates a dynamic source code file object
 *
 * This is an example of how we can prepare a dynamic java source code for compilation.
 * This class reads the java code from a string and prepares a JavaFileObject
 *
 */
class DynamicJavaSourceCodeObject extends SimpleJavaFileObject{
	private String qualifiedName ;
	private String sourceCode ;

	/**
	 * Converts the name to an URI, as that is the format expected by JavaFileObject
	 *
	 *
	 * @param fully qualified name given to the class file
	 * @param code the source code string
	 */
	protected DynamicJavaSourceCodeObject(String name, String code) {
		super(URI.create("string:///" +name.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
		this.qualifiedName = name ;
		this.sourceCode = code ;
	}

	@Override
	public CharSequence getCharContent(boolean ignoreEncodingErrors)
			throws IOException {
		return sourceCode ;
	}

	public String getQualifiedName() {
		return qualifiedName;
	}

	public void setQualifiedName(String qualifiedName) {
		this.qualifiedName = qualifiedName;
	}

	public String getSourceCode() {
		return sourceCode;
	}

	public void setSourceCode(String sourceCode) {
		this.sourceCode = sourceCode;
	}
}

Once you have this customized JavaFileObject implemented, make sure you create objects of this for each of your dynamic java source code entity.

/*Creating dynamic java source code file object*/
SimpleJavaFileObject fileObject = new DynamicJavaSourceCodeObject ("com.accordess.ca.DynamicCompilationHelloWorld", sourceCode) ;
JavaFileObject javaFileObjects[] = new JavaFileObject[]{fileObject} ;
  • b) If the source code is from file system, then create JavaFileObject instances from the File objects read from the file system.
/*Java source files read from file system*/
File []files = new File[]{file1, file2} ;
Iterable<? extends JavaFileObject> compilationUnits1 =
           fileManager.getJavaFileObjectsFromFiles(Arrays.asList(files1));

3. Build a list of all your compilation units

/* Prepare a list of compilation units (java source code file objects) to input to compilation task*/
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(javaFileObjects);

4. If you need to provide any compilation options that you use in command line ‘javac’, such as ‘-d’, ‘-classpath’ and etc. Create a list of these options as a list of string objects.

		/*Prepare any compilation options to be used during compilation*/
		//In this example, we are asking the compiler to place the output files under bin folder.
		String[] compileOptions = new String[]{"-d", "bin"} ;
		Iterable<String> compilationOptionss = Arrays.asList(compileOptions);

5. Now, retrieve the compiler from ToolProvider.

/*Instantiating the java compiler*/
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

This gets the compiler from the current platform.

6. As a next step, get a standard file manager from compiler, this file manager helps us to customize how a compiler reads and writes to files.

/**
 * Retrieving the standard file manager from compiler object, which is used to provide
 * basic building block for customizing how a compiler reads and writes to files.
 *
 * The same file manager can be reopened for another compiler task.
 * Thus we reduce the overhead of scanning through file system and jar files each time
 */
StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(null, Locale.getDefault(), null);

7. Create a diagnostic collector object, which collects the compilation problems.

/*Create a diagnostic controller, which holds the compilation problems*/
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();

8. Create a compilation task from compiler object by passing all of the above building blocks as input as required.

/*Create a compilation task from compiler by passing in the required input objects prepared above*/
CompilationTask compilerTask = compiler.getTask(null, stdFileManager, diagnostics, compilationOptionss, null, compilationUnits) ;

Let us spend some time understanding this line.

Here is the declaration of the above method from JavaCompiler class

 CompilationTask getTask(Writer out,
                            JavaFileManager fileManager,
                            DiagnosticListener<? super JavaFileObject> diagnosticListener,
                            Iterable<String> options,
                            Iterable<String> classes,
                            Iterable<? extends JavaFileObject> compilationUnits);

out: is a writer object, if not null, this would be used to write all the compilation errors. If null, the errors are written to the standard error console, i.e., System.err.

fileManager: is an instance of the JavaFileManager, which abstracts programming language source and class files. In this context, file means an abstraction of regular files and other sources of data.

diagnosticListener: which acts as a listener to the compilation events happening and logs any issues found. The DiagnosticCollector, we have created here is impleting this listener and that stores all the compilation diagnostic messages. If this is not passed, the compilation issues will be logged on standard error console.

options: the compilation options to be passed during compilation, this can be null if there are no options to be used.

classes: class names (for annotation processing), null means no class names

compilationUnits: these are the list of JavaFileObject instances, that need to be compiled.

9. Finally, call the method ‘call’ on compilation task, which does the actual job, and returns ‘true’ on success or ‘false’ otherwise.

//Perform the compilation by calling the call method on compilerTask object.
boolean status = compilerTask.call();

10. On compilation failure, we can use the diagnostic collector to read the error messages and log them in specific format.

if (!status){//If compilation error occurs
	/*Iterate through each compilation problem and print it*/
	for (Diagnostic diagnostic : diagnostics.getDiagnostics()){
		System.out.format("Error on line %d in %s", diagnostic.getLineNumber(), diagnostic);
	}
}

If we need to compile another set of compilation units, just create another compilation task by passing the new set of compilation units and execute the call method on it.

Finally close the fileManager instance to flush out anything that is there in the buffer.

try {
			stdFileManager.close() ;//Close the file manager
	} catch (IOException e) {
			e.printStackTrace();
	}

that’s it. We are pretty much done with the example.  The piece of code mentioned over here doesn’t result into any errors. Introduce some error into the code string and play with it. By the way, after successful compilation the class file would be generated under the current folder if you don’t pass in the java options I mentioned over here. If you are passing the same java options I mentioned here, make sure you create a folder by the name ‘bin’ under your current folder. Otherwise this will result into an error.

Who will benefit from this feature?

Application server developers: Application server need to generate java files from JSP code and compile them dynamically, thus reducing the application hot deployment time.

IDEs and Developer Tools like Ant: This API helps them to load the compiler once and perform compilation as and when needed instead of loading an external compiler each time the code changes.

Here is the full-version of the example source code.

package com.accordess.ca;

import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Locale;
import java.util.logging.Logger;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

/**
 * A test class to test dynamic compilation API.
 *
 */
public class CompilerAPITest {
	final Logger logger = Logger.getLogger(CompilerAPITest.class.getName()) ;

	/**Java source code to be compiled dynamically*/
	static String sourceCode = "package com.accordess.ca;" +
		"class DynamicCompilationHelloWorld{" +
			"public static void main (String args[]){" +
				"System.out.println (\"Hello, dynamic compilation world!\");" +
			"}" +
		"}" ;

	/**
	 * Does the required object initialization and compilation.
	 */
	public void doCompilation (){
		/*Creating dynamic java source code file object*/
		SimpleJavaFileObject fileObject = new DynamicJavaSourceCodeObject ("com.accordess.ca.DynamicCompilationHelloWorld", sourceCode) ;
		JavaFileObject javaFileObjects[] = new JavaFileObject[]{fileObject} ;

		/*Instantiating the java compiler*/
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

		/**
		 * Retrieving the standard file manager from compiler object, which is used to provide
		 * basic building block for customizing how a compiler reads and writes to files.
		 *
		 * The same file manager can be reopened for another compiler task.
		 * Thus we reduce the overhead of scanning through file system and jar files each time
		 */
		StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(null, Locale.getDefault(), null);

		/* Prepare a list of compilation units (java source code file objects) to input to compilation task*/
		Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(javaFileObjects);

		/*Prepare any compilation options to be used during compilation*/
		//In this example, we are asking the compiler to place the output files under bin folder.
		String[] compileOptions = new String[]{"-d", "bin"} ;
		Iterable<String> compilationOptionss = Arrays.asList(compileOptions);

		/*Create a diagnostic controller, which holds the compilation problems*/
		DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();

		/*Create a compilation task from compiler by passing in the required input objects prepared above*/
		CompilationTask compilerTask = compiler.getTask(null, stdFileManager, diagnostics, compilationOptionss, null, compilationUnits) ;

		//Perform the compilation by calling the call method on compilerTask object.
		boolean status = compilerTask.call();

		if (!status){//If compilation error occurs
			/*Iterate through each compilation problem and print it*/
			for (Diagnostic diagnostic : diagnostics.getDiagnostics()){
				System.out.format("Error on line %d in %s", diagnostic.getLineNumber(), diagnostic);
			}
		}
		try {
			stdFileManager.close() ;//Close the file manager
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String args[]){
		new CompilerAPITest ().doCompilation() ;
	}

}

/**
 * Creates a dynamic source code file object
 *
 * This is an example of how we can prepare a dynamic java source code for compilation.
 * This class reads the java code from a string and prepares a JavaFileObject
 *
 */
class DynamicJavaSourceCodeObject extends SimpleJavaFileObject{
	private String qualifiedName ;
	private String sourceCode ;

	/**
	 * Converts the name to an URI, as that is the format expected by JavaFileObject
	 *
	 *
	 * @param fully qualified name given to the class file
	 * @param code the source code string
	 */
	protected DynamicJavaSourceCodeObject(String name, String code) {
		super(URI.create("string:///" +name.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
		this.qualifiedName = name ;
		this.sourceCode = code ;
	}

	@Override
	public CharSequence getCharContent(boolean ignoreEncodingErrors)
			throws IOException {
		return sourceCode ;
	}

	public String getQualifiedName() {
		return qualifiedName;
	}

	public void setQualifiedName(String qualifiedName) {
		this.qualifiedName = qualifiedName;
	}

	public String getSourceCode() {
		return sourceCode;
	}

	public void setSourceCode(String sourceCode) {
		this.sourceCode = sourceCode;
	}
}

I have also attached the eclipse project to this post. You can download and import this project into Eclipse IDE. You will be ready to play with it…!

Download the source code eclipse project here

Bookmark and Share

32 Responses to Generating Java classes dynamically through Java compiler API

  1. Ran Biron says:

    Note that you have to have the “tools.jar” in your classpath and that you’re not allowed to distribute it with your application (you’re not allowed to distribute the JDK).

  2. Den says:

    For web applications you can use the following component: http://www.servletsuite.com/servlets/evaltag.htm Lets you evaluate JSP code in the dynamic.

  3. Saurabh Shastri says:

    Is it something like jRebel killer?

  4. Lior Yaffe says:

    There is no restrictions on re-distributing tools.jar and even javac.exe itself look at the Java license agreement.

  5. Surjeet Kumar says:

    Thanks A Lot………………
    I’ve Downloaded This Code,Compile And Run It Successfully…..Actually I Was Looking For This From A Long Time That How We Can Generate Class File Through Java Program.Finally I Got….

  6. Markus says:

    Very helpful! Thank you.
    There is a small bug though if you want to declare the DynamicCompilationHelloWorld class as public.

    In line 108 it says: name.replaceAll(“.”, “/”)
    However, replaceAll uses regex and would replace the entire string with slashes. this prevented me of using it with a public class. using replace instead of replaceAll works.

  7. yokese says:

    When I compile and it executes ‘compilerTask.call()’ I get this in console:

    javacTask: directory not found: bin
    Usage: javacTask
    use -help for a list of possible options

    No idea what’s happening.

  8. Joan says:

    Hello,

    Have you ever tried to compile in-memory a class that implements an interface?
    I have an interface named CacheRule (in com/vpfw/proxy/logicRules/CacheRule.class).
    I have a class name “CacheRuleBean” that I compile in-memory.
    If this class does not implement “CacheRule”, compilations works. But if this class implements “CacheRule”, then the error is:
    java.lang.NoClassDefFoundError: com/vpfw/proxy/logicRules/CacheRule (wrong name: com/vpfw/proxy/logicRules/CacheRuleBean)

    Do you know what I’m doing wrong?

    Many thanks,
    Joan.

  9. Ozair Jr says:

    Good article, but replace

    protected DynamicJavaSourceCodeObject(String name, String code) {
    super(URI.create(“string:///” +name.replaceAll(“.”, “/”) + Kind.SOURCE.extension), Kind.SOURCE);

    for

    protected DynamicJavaSourceCodeObject(String name, String code) {
    super(URI.create(“string:///” +name.replaceAll(“\\.”, “/”) + Kind.SOURCE.extension), Kind.SOURCE);

  10. thank you, really interesting!

    I need exactly something like this, and even more, because I need to create JAR file – but this is only a matter of zipping the created classes (at least I hope so).

  11. Dino says:

    Hello,

    I have tried this sample code and I do not get any error yet I cannot find the class file of the code coming from the string.

    When you meant current folder where would that be if my Eclipse workspace for the project is “JavaCompilerAPI”.

    Looking forward to getting this running. I appreciate any help you can provide.

    Dino

    • Upendra says:

      Dino,

      If your project location is in /usr/app/JavaCompilerAPI, then the generated class file should be available /usr/app/JavaComplierAPI///DynamicCompilationHelloWorld.class

      For the example given in the blog the path should be this.
      /usr/app/JavaComplierAPI/bin/com/accordess/ca/DynamicCompilationHelloWorld.class

      Thanks,
      Upendra

  12. Aswini says:

    am trying to run this code and getting following errors:

    ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2
    JDWP exit error AGENT_ERROR_NO_JNI_ENV(183): [../../../src/share/back/util.c:820]

    Where is the problem. Thanks in advance

  13. Veeraprabu says:

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    /**
    * Retrieving the standard file manager from compiler object, which is used to provide
    * basic building block for customizing how a compiler reads and writes to files.
    *
    * The same file manager can be reopened for another compiler task.
    * Thus we reduce the overhead of scanning through file system and jar files each time
    */
    StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(null, Locale.getDefault(), null);//on this line i am getting below exception..

    Exception in thread “main” java.lang.NullPointerException
    at CompilerAPITest.doCompilation(CompilerAPITest.java:52)
    at CompilerAPITest.main(CompilerAPITest.java:85)

    any idea?…pls reply

  14. Ranjit Rout says:

    Hi,
    I have downloaded the source code from the above side and compile CompilerAPITest.java through javac command, two class files are generated .When trying to execute any one of the program using java command throwing exception .
    est
    Exception in thread “main” java.lang.NoClassDefFoundError: CompilerAPITest (wron
    g name: com/accordess/ca/CompilerAPITest)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:14
    1)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
    at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
    Could not find the main class: CompilerAPITest. Program will exit.

    I appreciate your help.
    Thanks in advance.
    Regards,
    Ranjit Rout

  15. Pingback: Dynamic Compiling Without Create Physical File | PHP Developer Resource

  16. Rahul says:

    That’s a good article. It works great as is.
    However if you make the class public..then I get this error
    Error on line 1 in string://////////////////////////////////////.java:1: class DynamicCompilationHelloWorld is public, should be declared in a file named DynamicCompilationHelloWorld.javaOutput: Hello, dynamic compilation world!

  17. selva says:

    Few points to be taken care while referring your classpath and pointing the compiler where you want to generate the class files.
    The below piece of code illustrates the same.

    List optionList = new ArrayList();
    optionList.addAll(Arrays.asList(“-classpath”, “.:/usr/local/tomcat/apache-tomcat-7.0.11/webapps/PROJECT/WEB-INF/classes”));
    optionList.addAll(Arrays.asList(“-d”, “/usr/local/tomcat/apache-tomcat-7.0.11/webapps/PROJECT/WEB-INF/classes/”));

    compiler.getTask(null, fileManager, null, optionList, null, compilationUnits1).call();

  18. Pingback: Java compiler resources

  19. alexK says:

    I get the same problem as ranjit when trying to acces the class and running the project in Spring Tool Suite as a Java project. If I do not get that it just cannot find the class.

  20. alexK says:

    OK It seems you need to access the class using reflection and the “wrong name ” error arise because it has to be a fully qualified name.

    It also seems that the only way to use the class is via reflection: This seems linked to the fact there is no source code.

    Interesting, and with hindsight almost obvious

  21. kami says:

    Hi,

    I have downloaded above project but getting following exception

    Exception in thread “main” java.lang.NullPointerException
    at com.accordess.ca.CompilerAPITest.doCompilation(CompilerAPITest.java:51)
    at com.accordess.ca.CompilerAPITest.main(CompilerAPITest.java:84)

    Any help would be appreciated.

  22. google says:

    Thank you for all your efforts on this web site. Debby loves making time for research and it’s easy to see why. We hear all regarding the dynamic form you deliver great tactics through this website and therefore inspire contribution from some other people about this area of interest plus our own princess is undoubtedly being taught so much. Take pleasure in the remaining portion of the year. You’re conducting a remarkable job.

  23. punpunm says:

    Thanks for this article, I always dig deep for my knowledge, I learn a lots in this.

    I want to ask a question in part 2b. What type of variable “fileManager”?
    Could you make a small example for this part? I think it will help other people for learning more :D

  24. Java Experience says:

    Some of JDK classes are automatically loaded into JVM memory which can be seen by using the verbose options

  25. Carlos says:

    Hi!

    very interesting, very Thanks!

    on the other hand… could be possible to work, or executed the compiled dinamically class?

    I don’t want the generated .class at disc, I would like to work with it in memory :)

    thanks!

  26. kamald says:

    Hi,
    I am getting below exception…

    Exception in thread “main” java.lang.NullPointerException
    at com.accordess.ca.CompilerAPITest.doCompilation(CompilerAPITest.java:51)
    at com.accordess.ca.CompilerAPITest.main(CompilerAPITest.java:84)

    what could be the problem, any suggestion ?

  27. Paul says:

    Hi, don’t suppose anyone figured out a way to debug in-memory compiled classes in eclipse with source code attached (say we saved the generated .java files to a tmp directory during in-mem compilation)?

    thanks, Paul

  28. Akhilesh Dubey says:

    Hi,
    I am getting below exception…

    Exception in thread “main” java.lang.NullPointerException
    at com.accordess.ca.CompilerAPITest.doCompilation(CompilerAPITest.java:51)
    at com.accordess.ca.CompilerAPITest.main(CompilerAPITest.java:84)

    Why So, Help plz…

  29. Suresh Anbarasan says:

    Hi Akhilesh Dubey
    You might have missed adding tools.jar in your class path.
    Add it and do compile and execute.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>