Janino

A super-small, super-fast Java compiler

View the Project on GitHub janino-compiler/janino

Breaking news!
Janino is now "Tee-ware", which means that the applicable license is still BSD, but you are free to buy a fan t-shirt on Teespring (one per CPU, or as many as you want 😉).
Support the software development and show your appreciation by purchasing Janino merch!
Janino Swoosh

Janino is a super-small, super-fast Java compiler.

Janino can not only compile a set of source files to a set of class files like JAVAC, but also compile a Java expression, a block, a class body, one .java file or a set of .java files in memory, load the bytecode and execute it directly in the running JVM.

JANINO is integrated with Apache Commons JCI ("Java Compiler Interface") and JBoss Rules / Drools.

JANINO can also be used for static code analysis or code manipulation.

Table of Contents

Getting Started

What's this all about? Check out this PDF presentation for a quick start. (Notice that some of the information is outdated, e.g. the references to CODEHAUS, which has passed away some time 2015.)

Properties

The major design goal was to keep the compiler small and simple, while providing an (almost) complete Java compiler.

The following elements of the Java programming language are implemented (or, where noted, partially implemented):

JANINO supports all these language features, even if it runs in an older JRE!

Requirements

JANINO only requires a Java 7 (or later) JRE or later, not a JDK. It has no dependencies whatsoever on any third-party libraries.

JANINO is routinely tested against the following JREs:

Java versionJRE version Release DateStatus
7 jdk1.7.0_21 (32 bit) 2011-07-28 passed
8 adopt_openjdk-8.0.292.10-hotspot? passed
11 adopt_openjdk-11.0.11.9-hotspot ? passed
17 adopt_openjdk-17.0.1+12 ? passed

Limitations

The following elements of the Java programming language are not implemented (or, where noted, are only partially implemented):

License

JANINO is available under the New BSD License.

Download

Packages

If you are using MAVEN, add this entry to your POM file:

<dependency>
    <groupId>org.codehaus.janino</groupId>
    <artifactId>janino</artifactId>
    <version>***</version>
</dependency>
<dependency>
    <groupId>org.codehaus.janino</groupId>
    <artifactId>janino</artifactId>
    <version><var>***</var></version>
</dependency>
<dependency>
    <groupId>org.codehaus.janino</groupId>
    <artifactId>janino</artifactId>
    <version><var>***</var></version>
</dependency>

(Replace "***" with the latest version of JANINO.)

If you are not using MAVEN, do the following instead:

Security / integrity

All packages are signed with one of these PGP keys. Make sure to check the package signatures to prevent supply chain attacks:
Key #1 Key #2 Key #3

Installation

If you're using an IDE like ECLIPSE, you can optionally download "janino-version-sources.jar" and "commons-compiler-version-sources.jar" and configure them as the source attachments. That'll get you tooltip JAVADOC and source level debugging into the JANINO libraries.

Use one of the features, e.g. the "expression evaluator", in your program:

import org.codehaus.janino.*;

ExpressionEvaluator ee = new ExpressionEvaluator();
ee.cook("3 + 4");
System.out.println(ee.evaluate()); // Prints "7".

Compile, run, ... be happy!

Examples

The ShippingCost class demonstrates how easy it is to use Janino as an expression evaluator.

The ExpressionDemo class implements a command line-based test environment for the expression evaluator.

The ScriptDemo class implements a command line-based test environment for the script evaluator.

The ClassBodyDemo class implements a command line-based test environment for the class body evaluator.

The DeclarationCounter class implements a command-line utility that counts class, interface, field and local variable declarations in a set of Java source files.

Documentation

Change Log

The complete version change log is available here.

JAVADOC

The full JAVADOC documentation for JANINO is available online for the latest version, and for download for all versions.

Books

The specifications of the Java programming language:

Books that refer to the JANINO technology:

Links

Articles about JANINO

Some open source projects that use JANINO

Other Java compiler projects and products that I know about

Basic Examples

Janino as an Expression Evaluator

Say you build an e-commerce system, which computes the shipping cost for the items that the user put into his/her shopping cart. Because you don't know the merchant's shipping cost model at implementation time, you could implement a set of shipping cost models that come to mind (flat charge, by weight, by number of items, ...) and select one of those at run-time.

In practice, you will most certainly find that the shipping cost models you implemented will rarely match what the merchant wants, so you must add custom models, which are merchant-specific. If the merchant's model changes later, you must change your code, re-compile and re-distribute your software.

Because this is so unflexible, the shipping cost expression should be specified at run-time, not at compile-time. This implies that the expression must be scanned, parsed and evaluated at run-time, which is why you need an expression evaluator.

A simple expression evaluator would parse an expression and create a "syntax tree". The expression "a + b * c", for example, would compile into a "Sum" object who's first operand is parameter "a" and who's second operand is a "Product" object who's operands are parameters "b" and "c". Such a syntax tree can evaluated relatively quickly. However, the run-time performance is about a factor of 100 worse than that of "native" Java code executed directly by the JVM. This limits the use of such an expression evaluator to simple applications.

Also, you may want not only do simple arithmetics like "a + b * c % d", but take the concept further and have a real "scripting" language which adds flexibility to your application. Since you know the Java programming language already, you may want to have a syntax that is similar to that of the Java programming language.

All these considerations lead to compilation of Java code at run-time, like some engines (e.g. JSP engines) already do. However, compiling Java programs with ORACLE's JDK is a relatively resource-intensive process (disk access, CPU time, ...). This is where Janino comes into play... a light-weight, "embedded" Java compiler that compiles simple programs in memory into JVM bytecode which executes within the JVM of the running program.

OK, now you are curious... this is how you use the ExpressionEvaluator:

package foo;

import java.lang.reflect.InvocationTargetException;

import org.codehaus.commons.compiler.CompileException;
import org.codehaus.janino.ExpressionEvaluator;

public class Main {

    public static void
    main(String[] args) throws CompileException, InvocationTargetException {

        // Now here's where the story begins...
        ExpressionEvaluator ee = new ExpressionEvaluator();

        // The expression will have two "int" parameters: "a" and "b".
        ee.setParameters(new String[] { "a", "b" }, new Class[] { int.class, int.class });

        // And the expression (i.e. "result") type is also "int".
        ee.setExpressionType(int.class);

        // And now we "cook" (scan, parse, compile and load) the fabulous expression.
        ee.cook("a + b");

        // Eventually we evaluate the expression - and that goes super-fast.
        int result = (Integer) ee.evaluate(new Object[] { 19, 23 });
        System.out.println(result);
    }
}

Notice: If you pass a string literal as the expression, be sure to escape all Java special characters, especially backslashes.

The compilation of the expression takes 670 microseconds on my machine (2 GHz P4), and the evaluation 0.35 microseconds (approx. 2000 times faster than compilation).

There is a sample program "ExpressionDemo" that you can use to play around with the ExpressionEvaluator, or you can study ExpressionDemo's source code to learn about ExpressionEvaluator's API:

$ java -cp janino.jar:commons-compiler.jar \
> org.codehaus.commons.compiler.samples.ExpressionDemo \
> -help
Usage:
  ExpressionDemo { <option> } <expression> { <parameter-value> }
Compiles and evaluates the given expression and prints its value.
Valid options are
 -et <expression-type>                        (default: any)
 -pn <comma-separated-parameter-names>        (default: none)
 -pt <comma-separated-parameter-types>        (default: none)
 -te <comma-separated-thrown-exception-types> (default: none)
 -di <comma-separated-default-imports>        (default: none)
 -help
The number of parameter names, types and values must be identical.
$ java -cp janino.jar:commons-compiler.jar \
> org.codehaus.commons.compiler.samples.ExpressionDemo \
> -et double \
> -pn x \
> -pt double \
> "Math.sqrt(x)" \
> 99
Result = 9.9498743710662
$

Janino as a Script Evaluator

Analogously to the expression evaluator, a ScriptEvaluator API exists that compiles and processes a Java "block", i.e. the body of a method. If a return value other than "void" is defined, then the block must return a value of that type.

As a special feature, it allows methods to be declared. The place and order of the method declarations is not relevant.

Example:

package foo;

import java.lang.reflect.InvocationTargetException;

import org.codehaus.commons.compiler.CompileException;
import org.codehaus.janino.ScriptEvaluator;

public class Main {

    public static void
    main(String[] args) throws CompileException, NumberFormatException, InvocationTargetException {

        ScriptEvaluator se = new ScriptEvaluator();

        se.cook(
            ""
            + "static void method1() {\n"
            + "    System.out.println(1);\n"
            + "}\n"
            + "\n"
            + "method1();\n"
            + "method2();\n"
            + "\n"
            + "static void method2() {\n"
            + "    System.out.println(2);\n"
            + "}\n"
        );

        se.evaluate();
    }
}

As for the expression compiler, there is a demo program "ScriptDemo" for you to play with the ScriptEvaluator API:

$ java -cp janino.jar:commons-compiler.jar \
> org.codehaus.commons.compiler.samples.ScriptDemo -help
Usage:
  ScriptDemo { <option> } <script> { <parameter-value> }
Valid options are
 -rt <return-type>                            (default: void)
 -pn <comma-separated-parameter-names>        (default: none)
 -pt <comma-separated-parameter-types>        (default: none)
 -te <comma-separated-thrown-exception-types> (default: none)
 -di <comma-separated-default-imports>        (default: none)
 -help
The number of parameter names, types and values must be identical.
$ java -cp janino.jar:commons-compiler.jar \
> org.codehaus.commons.compiler.samples.ScriptDemo '
> for (int i = 0; i < 3; i++) {
>     System.out.println("HELLO");
> }'
HELLO
HELLO
HELLO
Result = (null)
$

Check the source code of ScriptDemo to learn more about the ScriptEvaluator API.

Janino as a Class Body Evaluator

Analogously to the expression evaluator and the script evaluator, a ClassBodyEvaluator exists that compiles and processes the body of a Java class, i.e. a series of method and variable declarations. If you define a contract that the class body should define a method named "main()", then your script will look almost like a "C" program:

public static void
main(String[] args) {
    System.out.println(java.util.Arrays.asList(args));
}

The "ClassBodyDemo" program (source code) demonstrates this:

$ java -cp janino.jar:commons-compiler.jar \
> org.codehaus.commons.compiler.samples.ClassBodyDemo -help
Usage:
  ClassBodyDemo <class-body> { <argument> }
  ClassBodyDemo -help
If <class-body> starts with a '@', then the class body is read
from the named file.
The <class-body> must declare a method "public static void main(String[])"
to which the <argument>s are passed. If the return type of that method is
not VOID, then the returned value is printed to STDOUT.
$ java -cp janino.jar:commons-compiler.jar \
> org.codehaus.commons.compiler.samples.ClassBodyDemo '
> public static void
> main(String[] args) {
>     System.out.println(java.util.Arrays.asList(args));
> }' \
> a b c
[a, b, c]
$

Janino as a Simple Compiler

The SimpleCompiler compiles a single .java file ("compilation unit"). Opposed to normal Java compilation, that compilation unit may declare more than one public type.

Example:

// This is file "Hello.java", but it could have any name.

public
class Foo {

    public static void
    main(String[] args) {
        new Bar().meth();
    }
}

public
class Bar {

    public void
    meth() {
        System.out.println("HELLO!");
    }
}

It returns a ClassLoader from which you can retrieve the classes that were compiled.

To run this, type:

$ java -cp janino.jar:commons-compiler.jar \
> org.codehaus.janino.SimpleCompiler -help
Usage:
    org.codehaus.janino.SimpleCompiler <source-file> <class-name> { <argument> }
Reads a compilation unit from the given <source-file> and invokes method
"public static void main(String[])" of class <class-name>, passing the
given <argument>s.
$ java -cp janino.jar:commons-compiler.jar \
> org.codehaus.janino.SimpleCompiler \
> Hello.java Foo
HELLO!
$

Janino as a Compiler

The Compiler compiles a set of .java files ("compilation units"), and creates .class files. Each compilation unit may declare a different package, and the compilation units may reference each other, even in a circular manner.

Example:


ICompiler compiler = compilerFactory.newCompiler();

compiler.compile(new File("pkg1/A.java"), new File("pkg2/B.java"));

However the Compiler can be reconfigured to read the compilation units from a different source, and/or to store the classes in a different place, e.g. into a Map, which is then loaded into the VM through a ClassLoader:


ICompiler compiler = compilerFactory.newCompiler();

// Store generated .class files in a Map:
Map<String, byte[]> classes = new HashMap<String, byte[]>();
compiler.setClassFileCreator(new MapResourceCreator(classes));

// Now compile two units from strings:
compiler.compile(new Resource[] {
    new StringResource(
        "pkg1/A.java",
        "package pkg1; public class A { public static int meth() { return pkg2.B.meth(); } }"
    ),
    new StringResource(
        "pkg2/B.java",
        "package pkg2; public class B { public static int meth() { return 77;            } }"
    ),
});

// Set up a class loader that uses the generated classes.
ClassLoader cl = new ResourceFinderClassLoader(
    new MapResourceFinder(classes),    // resourceFinder
    ClassLoader.getSystemClassLoader() // parent
);

Assert.assertEquals(77, cl.loadClass("pkg1.A").getDeclaredMethod("meth").invoke(null));

Advanced Examples

Janino as a Source Code Class Loader

The JavaSourceClassLoader extends Java's java.lang.ClassLoader class with the ability to load classes directly from source code.

To be precise, if a class is loaded through this class loader, it searches for a matching ".java" file in any of the directories specified by a given "source path", reads, scans, parses and compiles it and defines the resulting classes in the JVM. As necessary, more classes are loaded through the parent class loader and/or through the source path. No intermediate files are created in the file system.

Example:

// srcdir/pkg1/A.java

package pkg1;

import pkg2.*;

public class A extends B {
}
// srcdir/pkg2/B.java

package pkg2;

public class B implements Runnable {
    public void run() {
        System.out.println("HELLO");
    }
}
// Sample code that reads, scans, parses, compiles and loads
// "A.java" and "B.java", then instantiates an object of class
// "A" and invokes its "run()" method.
ClassLoader cl = new JavaSourceClassLoader(
    this.getClass().getClassLoader(),  // parentClassLoader
    new File[] { new File("srcdir") }, // optionalSourcePath
    (String) null,                     // optionalCharacterEncoding
    DebuggingInformation.NONE          // debuggingInformation
);

// Load class A from "srcdir/pkg1/A.java", and also its superclass
// B from "srcdir/pkg2/B.java":
Object o = cl.loadClass("pkg1.A").newInstance();

// Class "B" implements "Runnable", so we can cast "o" to
// "Runnable".
((Runnable) o).run(); // Prints "HELLO" to "System.out".

If the Java source is not available in files, but from some other storage (database, main memory, ...), you may specify a custom ResourceFinder instead of the directory-based source path.

If you have many source files and you want to reduce the compilation time, you may want to use the CachingJavaSourceClassLoader, which uses a cache provided by the application to store class files for repeated use.

jsh - the Java shell

JANINO has a sister project "jsh" which implements a "shell" program similar to "bash", "ksh", "csh" etc., but with Java syntax.

Janino as a Command-Line Java Compiler

The Compiler class mimics the behavior of ORACLE's javac tool. It compiles a set of "compilation units" (i.e. Java source files) into a set of class files.

Using the "-warn" option, Janino emits some probably very interesting warnings which may help you to "clean up" the source code.

The BASH script "bin/janinoc" implements a drop-in replacement for ORACLE's JAVAC utility:

$ janinoc -sourcepath src -d classes src/com/acme/MyClass.java
$ janinoc -help
A drop-in replacement for the JAVAC compiler, see the documentation for JAVAC
Usage:

  java java.lang.Compiler [ <option> ] ... <source-file> ...

Supported <option>s are:
  -d <output-dir>           Where to save class files
  -sourcepath <dirlist>     Where to look for other source files
  -classpath <dirlist>      Where to look for other class files
  -extdirs <dirlist>        Where to look for other class files
  -bootclasspath <dirlist>  Where to look for other class files
  -encoding <encoding>      Encoding of source files, e.g. "UTF-8" or "ISO-8859-1"
  -verbose
  -g                        Generate all debugging info
  -g:none                   Generate no debugging info (the default)
  -g:{source,lines,vars}    Generate only some debugging info
  -rebuild                  Compile all source files, even if the class files
                            seem up-to-date
  -help

The default encoding in this environment is "UTF-8".
$

Janino as an ANT Compiler

You can plug JANINO into the ANT utility through the AntCompilerAdapter class. Just make sure that janino.jar is on the class path, then run ANT with the following command-line option:

-Dbuild.compiler=org.codehaus.janino.AntCompilerAdapter

Janino as a TOMCAT Compiler

If you want to use JANINO with TOMCAT, just copy the "janino.jar" file into TOMCAT's "common/lib" directory, and add the follwing init parameter section to the JSP servlet definition in TOMCAT's "conf/web.xml" file:

<init-param>
    <param-name>compiler</param-name>
    <param-value>org.codehaus.janino.AntCompilerAdapter</param-value>
</init-param>

Janino as a Code Analyser

Apart from compiling Java code, JANINO can be used for static code analysis: Based on the AST ("abstract syntax tree") produced by the parser, the Traverser walks through all nodes of the AST, and derived classes can do all kinds of analyses on them, e.g. count declarations:

$ java org.codehaus.janino.samples.DeclarationCounter DeclarationCounter.java
Class declarations:     1
Interface declarations: 0
Fields:                 4
Local variables:        4
$

This is the basis for all these neat code metrics and style checking.

Janino as a Code Manipulator

If you want to read a Java compilation unit into memory, manipulate it, and then write it back to a file for compilation, then all you have to do is:

// Read the compilation unit from Reader "r" into memory.
Java.CompilationUnit cu = new Parser(new Scanner(fileName, r)).parseCompilationUnit();

// Manipulate the AST in memory.
// ...

// Convert the AST back into text.
UnparseVisitor.unparse(cu, new OutputStreamWriter(System.out));

The AstTest.testMoveLocalVariablesToFields() test case demostrates how to manipulate an AST.

Alternative Compiler Implementations

JANINO can be configured to use not its own Java compiler, but an alternative implementation. Alternative implementations must basically implement the interface ICompilerFactory. One such alternative implementation is based on the javax.tools API, and is shipped as part of the JANINO distribution: commons-compiler-jdk.jar.

Basically there are two ways to switch implementations:

The performance of the code generated by the JDK-based implementation is the same as with the JANINO implementation; the compilation, however, is slower by a factor of 22 (measured by compiling the expression "a + b"; two ints, see below). The main reason is that the javax.tools compiler loads the required JRE classes through the classpath, when JANINO uses classes that are already loaded into the running JVM. Thus, CompilerDemo (the drop-in replacement for JAVAC) is not faster than JAVAC (measured by compiling the Janino source code, see below).

ActivityJaninoJDK
Compile expression "a + b"0.44 ms9.84 ms
Compile Janino source code6.417 s5.864 s

(Measured with JDK 17 and JRE 17.)

Security

Warning: JRE 17 reports "System::setSecurityManager will be removed in a future release", so you should not use the Janino sandbox with JRE 17+.

Because the bytecode generated by JANINO has full access to the JRE, security problems can arise if the expression, script, class body or compilation unit being compiled and executed contains user input.

If that user is an educated system administrator, he or she can be expected to use JANINO responsibly and in accordance with documentation and caveats you provide; however if the user is an intranet or internet user, no assumtions should be made about how clumsy, frivolous, creative, single-minded or even malicious he or she could be.

JANINO includes a very easy-to-use security API, which can be used to lock expressions, scripts, class bodies and compilation units into a "sandbox", which is guarded by a Java security manager:

import java.security.Permissions;
import java.security.PrivilegedAction;
import java.util.PropertyPermission;
import org.codehaus.janino.ScriptEvaluator;
import org.codehaus.commons.compiler.Sandbox;

public class SandboxDemo {

    public static void
    main(String[] args) throws Exception {

        // Create a JANINO script evaluator. The example, however, will work as fine with
        // ExpressionEvaluators, ClassBodyEvaluators and SimpleCompilers.
        ScriptEvaluator se = new ScriptEvaluator();
        se.setDebuggingInformation(true, true, false);

        // Now create a "Permissions" object which allows to read the system variable
        // "foo", and forbids everything else.
        Permissions permissions = new Permissions();
        permissions.add(new PropertyPermission("foo", "read"));

        // Compile a simple script which reads two system variables - "foo" and "bar".
        PrivilegedAction<?> pa = se.createFastEvaluator((
            "System.getProperty(\"foo\");\n" +
            "System.getProperty(\"bar\");\n" +
            "return null;\n"
        ), PrivilegedAction.class, new String[0]);

        // Finally execute the script in the sandbox. Getting system property "foo" will
        // succeed, and getting "bar" will throw a
        //    java.security.AccessControlException: access denied (java.util.PropertyPermission bar read)
        // in line 2 of the script. Et voila!
        Sandbox sandbox = new Sandbox(permissions);
        sandbox.confine(pa);
    }
}

The official documentation of the Java security manager is ORACLE: Java Essentials: The Security Manager. Actions that are guarded by permissions include:

These are the "really evil things" that an attacker might do. However actions that are not guarded are:

Luckily, the Thread constructor does some reflection, so thread creation by scripts can be prevented by not allowing new RuntimePermission("accessDeclaredMembers").

Memory allocation can not be guarded, however, com.sun.management.ThreadMXBean.getThreadAllocatedBytes(long threadId) appears to count the number of bytes ever allocated by a thread, and may thus be a good measure to detect excessive memory allocation by a running script.

Debugging

The generated classes can be debugged interactively, even though they were created on-the-fly.

All that needs to be done is set two system properties, e.g. when starting the JVM:

$ java \
> ... \
> -Dorg.codehaus.janino.source_debugging.enable=true \
> -Dorg.codehaus.janino.source_debugging.dir=C:\tmp \
> ...

(The second property is optional; if not set, then the temporary files will be created in the default temporary-file directory.)

When JANINO scans an expression, script, class body or compilation unit, it stores a copy of the source code in a temporary file which the debugger accesses through its source path. (The temporary file will be deleted when the JVM terminates.)

Then when you debug your program

Debugging

, you can step right into the generated code

Debugging

, and debug it:

Debugging

As you can see, you can even inspect and modify fields and variables - everything your debugger supports.

Contact

Reporting bugs

If you think you have found a bug in Janino, proceed as follows:

Requesting support

If you require support, this is the place to ask for it.

Requesting new features

Feel free to submit feature requests here.

Feedback

I appreciate your feedback. Let me know how you want to utilize Janino, if you find it useful, or why you cannot use it for your project.

Developers

Source Code Repository

The JANINO code repository is here. You have to check out at least the following projects:

Optional:

Then you can build the JAR files, the source archives and the JAVADOC archives by running mvn install in the janino-parent project.

Contributing

If you want to contribute, turn to me: arno att unkrig dott de. Any support is welcome, be it bug reports, feature requests, documentation reviews or other work. Help building a better world with JANINO!