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:
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.
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!
