Spring Native Beta Release

Roman Ivanov
Geek Culture
Published in
4 min readMar 15, 2021

--

Recently, the team porting Spring to GraalVM released its first major release — the Spring Native Beta. Together with the creators of GraalVM, they managed to fix many bugs in the compiler and in Spring. Now the project has official support, its own release cycle, and can be used.

The most important obstacle to porting code from JVM to binary is the problem of using features unique to java — reflection, classpath handling, dynamic class loading, etc.

According to the documentation, the key differences between the regular JVM and the native implementation are as follows:

Static analysis of the entire application is performed at build time.

Unused components are removed at build time.

Reflection, resources, and dynamic proxies can only be configured with additional configurations.

All components are fixed in Classpath at build time.

No lazy class loading: everything supplied in executables will be loaded into memory at load time. For example, for the Class.forName (“myClass”) call to work correctly, you need to have myClass in the configuration file. If no class is found in the configuration file that is requested to dynamically load the class, a ClassNotFoundException will be thrown

Some of the code will be run at build time to link the components properly. For example, tests.

In sprung itself, reflection, proxy creation, and lazy initialization are found almost everywhere, so all configurations had to be handled carefully, because of this it took more than a year to release.

In the course of the research, a new component, Spring AOT, was created that is responsible for all the necessary conversions of your code to a Graal VM digestible format.

Spring AOT parses the code and uses it to create configuration files such as native-image.properties, reflection-config.json, proxy-config.json, or resource-config.json

Since Graal VM supports initial configuration through static files, these files are placed in the META-INF/native-image directory during the build.

Each builder has a different plugin that activates Spring AOT. For maven it is spring-aot-maven-plugin, respectively for Gradle, it is spring-aot-gradle-plugin.In order to add Gradle plugin to your project you only need one line:

plugins {id ‘org.springframework.experimental.aot’ version ‘0.9.0’}

The plugin tries to configure as many components as possible, performing preliminary conversions on all program components needed to improve compatibility.

If it fails to do so, you will need to add this data yourself. This can be done manually by correcting the configuration files, or by using specially created annotations.

For example, for cases where components are implemented using WebClient, you can use the annotation from org.springframework.nativex.hint to specify which type we will handle:

@TypeHint(types = Data.class, typeNames = “com.example.webclient.Data$SuperHero”)
@SpringBootApplication
public class WebClientApplication { // … }

Here we specify that we will serialize the Data class, which has a subclass SuperHero. At build time, a client will be created for us in advance that can work with this data type.

Since GraalVM doesn’t support working with dynamic proxies, the @ProxyHint annotation is created to support working with java.lang.reflect.Proxies.

You can apply it like this, for example:

@ProxyHint(types = { org.hibernate.Session.class, org.springframework.orm.jpa.EntityManagerProxy.class })

If you want to pull any resources into the image, you should use the @ResourceHint annotation:

@ResourceHint(patterns = “com/mysql/cj/TlsSettings.properties”)

To specify which classes/packages should be initialized explicitly at build or runtime, use the @InitializationHint annotation:

@InitializationHint(types = org.h2.util.Bits.class, initTime = InitializationTime.BUILD)

The @NativeHint annotation was created to bring all of these annotations together in a compact way:

@Repeatable(NativeHints.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeHint

All together it would look like this, for example:

@NativeHint( 
trigger = Driver.class,
options = “ — enable-all-security-services”,
types =
@TypeHint(types = { FailoverConnectionUrl.class // … }),
resources = {
@ResourceHint(patterns = “com/mysql/cj/TlsSettings.properties”),
@ResourceHint(patterns = “com.mysql.cj.LocalizedErrorMessages”, isBundle = true) })

As a trigger, we choose the class whose presence in the classpath should cause the configuration to be built.

All active annotations are taken into account at compile-time and converted into the Graal VM configuration by the Spring AOT plugin.

Spring Native is already included in the release cycle, you can pick up the template directly from start.spring.io. Since support for JPA and other spring components is already implemented, you can build a simple CRUD application right away. If you need to specify additional Graal VM parameters when building, you can add them using the BP_NATIVE_IMAGE_BUILD_ARGUMENTS environment variable in the Spring AOT plugin if building through Buildpacks, or using the “<buildArgs>” configuration element in pom.xml if you build through the native-image-maven-plugin.

Actually, run the commands mvn spring-boot: build-image or gradle bootBuildImage and the image will start building. It is worth noting that the builder requires over 7 GB of memory for the build to be successful. On my machine, it took no more than 5 minutes to build and upload the images. The image is very compact with only 60 MB. The application started in 0.022 seconds! This is an incredible result. More and more companies are moving to K8s and application startup as well as resources used are very important in today’s world, this technology allows Spring to become the framework number one for all types of microservices, even for FaaS implementations where cold start speed is very important.

--

--