GraalVM native support

Apache FreeMarker is compatible with Ahead-of-Time (AOT) compilation using GraalVM as of version 2.3.35, but with two big caveats:

  • Custom Java classes that are exposed to the templates via the data model must be explicitly listed in the GraalVM native reflection configuration, or else the FreeMarker templates will not see their members. That's because the templates are not compiled, and has to be able to discover the exposed classes on runtime via Java reflection.

  • If templates are stored as Java resources, the resources also must be explicitly configured to be accessible on runtime, or else FreeMarker will not find the templates.

Let's say you have this class:

package com.example;

public class User {
    private String name;
    private String email;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

Then you put an instance of that into the data model with name user, and access it from a template like this, which you put into src/main/resources/templates:

Template
Hello ${user.name}!

Your e-mail address is: ${user.email}

Then you configure FreeMarker to load the templates from the class path:

Configuration cfg = new Configuration(Configuration.VERSION_...);
...
cfg.setClassForTemplateLoading(this.getClass(), "/templates");

Under normal Java, all this would just work. But under GraalVM native, if it finds the template at all, the template processing will fail with freemarker.core.InvalidReferenceException saying that "user.name has evaluated to null or missing". That's because FreeMarker tries to discover the methods of the User class via Java reflection, but such information is by default not available in GraalVM native. You need to add a reflection configuration file like this, which we could put for example into src/main/graalvm-native-config/reflection-config.json:

[
  {
    "name": "com.example.User",
    "allPublicMethods": true
  }
]

Then this has to be referred in the GraalVM native build. For example in Gradle it would be something like this:

graalvmNative {
    binaries.all {
        ...
        configurationFileDirectories.from(file("src/main/graalvm-native-config"))
        resources.autodetect() // Needed to find the templates
        ...
    }
}

If you are running native-image form command line instead, then the corresponding command-line options are (here we assume that template are in src/main/resources/templates):

  • -H:ReflectionConfigurationFiles=./src/main/graalvm-native-config/reflection-config.json

  • -H:IncludeResources=^templates/.*

After these, the templates should now work.

Note:

This example was written for GraalVM 21. GraalVM and its Gradle plugin is still evolving, so above can become outdated, or not the best practice anymore. Feel free to report any such issue!

You can also find a working example in the FreeMarker source code; see the freemarker-test-graalvm-native subproject there!

Please see the GraalVM documentation for more details!