Graal VM’s AOT compilation invokes the native-image executable at build time to run all execution paths starting from the application’s entry point. While this works for simpler applications, from time to time, you will run into projects and code that prove incompatible with the native image tooling and need small tweaks. In this post, we will take a look at Substitutions in Graal VM and how incompatible code can be replaced at AOT build time.
This post specifically requires and focuses on:
22.3.x
For this exercise, I am going to focus on Apache Xerces, which provides high-performance, fully compliant XML parsers in the Apache Xerces family. If your project is a consumer of the xercesImpl library, (or does anything with XML parsing or the SAML2 protocol), you might receive the following error when you build and try to launch a native image with Graal VM:
Caused by: java.lang.RuntimeException: internal error
at org.apache.xerces.impl.dv.xs.XSSimpleTypeDecl.applyFacets1(Unknown Source)
at org.apache.xerces.impl.dv.xs.BaseSchemaDVFactory.createBuiltInTypes(Unknown Source)
at org.apache.xerces.impl.dv.xs.SchemaDVFactoryImpl.createBuiltInTypes(Unknown Source)
at org.apache.xerces.impl.dv.xs.SchemaDVFactoryImpl.<clinit>(Unknown Source)
... 46 common frames omitted
This is because XSSimpleTypeDecl
from Apache Xerces contains the following code:
void applyFacets1(XSFacets facets, short presentFacet, short fixedFacet) {
try {
// stuff happens...
internalPrivateMethodHere();
} catch (InvalidDatatypeFacetException e) {
throw new RuntimeException("internal error");
}
fIsImmutable = true;
}
Unfortunately, the original InvalidDatatypeFacetException
is swallowed here which is a very poor practice. Without code modifications to get access to the original error, it’s pretty impossible to determine the root cause and figure out exactly why the application is crashing. This is where Graal VM Substitions can help.
Graal VM provides a substitution mechanism to handle scenarios where the offending source code is for whatever reason unavailable or cannot easily be changed. At AOT compile-time, Graal VM native image can change and transform specific bytecode by replacing or deleting it. Substitutions are typically developed in Java, and we will have to build a small substitution that would allow us to see the original exception by logging it.
Modify your project to include the Graal VM SDK dependency and make sure it’s only available at compile-time. Then we can start with the following outline:
/*
You can name your class anything you want.
You need to make sure the class is marked as final.
*/
@TargetClass(org.apache.xerces.impl.dv.xs.XSSimpleTypeDecl.class)
public final class XSSimpleTypeDecl {
@Substitute
void applyFacets1(XSFacets facets, short presentFacet, short fixedFacet) {
try {
// stuff happens...
internalPrivateMethodHere();
} catch (InvalidDatatypeFacetException e) {
e.printStackTrace();
throw new RuntimeException("internal error", e);
}
fIsImmutable = true;
}
}
Of course, the above substitution would not compile because:
internalPrivateMethodHere();
which is an internal private method; the name is specifically chosen for maximum effect.fIsImmutable
field.To solve the first issue, we can use an alias to refer to the original method:
@Alias
void internalPrivateMethodHere() throws InvalidDatatypeFacetException {
// No implementation body is needed
}
An @Alias
here allows Graal VM to reference the original method in the targetted class and the AOT compilation will make the correct substitution.
To solve the second issue, we will use the same approach to alias the needed field:
@Alias
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.None)
private boolean fIsImmutable;
If you build and launch the native image, you should now see the exception properly logged. In this case, it turns out that specific resources on the classpath need to be registered with the native image tooling. In a Spring Boot application, this can be done via:
hints.resources()
.registerResourceBundle("org/apache/xml/security/resource/xmlsecurity")
.registerResourceBundle("org/apache/xerces/impl/msg/XMLSchemaMessages")
.registerResourceBundle("org/apache/xerces/impl/xpath/regex/message")
When you are done with the fix, the substitution can be removed. Then, onto the next error!
If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to send us a note and ask about consulting and support services.
I hope this review was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways.
Happy Coding,
Monday-Friday
9am-6pm, Central European Time
7am-1pm, U.S. Eastern Time
Monday-Friday
9am-6pm, Central European Time