A Step by Step guide to create Native Executable Reactive CRUD RESTful APIs using GraalVM + Spring Boot + Spring R2DBC with H2 in-memory database
Last modified: 14 Sep, 2020Introduction
The focus of this article is to build a Native Executable Reactive CRUD RESTful API built using GraalVM + Spring Boot.
This is an extension to the previously published article - A Step by Step guide to create Reactive CRUD RESTful APIs using Spring Boot + Spring Data R2DBC with H2 in-memory database.
The core principles of implementing Reactive CRUD RESTful APIs remain the same to the previous article, and this with more emphasis on the necessary changes need to support creating Native Executable Build using GraalVM.
Technology stack
- GraalVM Community 20.2.0 with OpenJDK 11
- Spring Boot v2.4.0-M2
- Spring Framework v5.3.0-M2
- R2DBC H2 v0.8.4 with H2 In-memory Database
- Maven v3.6.3
Why GraalVM ?
GraalVM
is a high-performance runtime that provides significant improvements in application performance and efficiency which is ideal for microservices. It is a Java VM and JDK based on HotSpot/OpenJDK, implemented in Java. It supports additional programming languages.
GraalVM
provides benefits by running Java Applications faster by creating ahead-of-time compiled native (system-dependent) machine code resulting the binary image to be executed natively.
Running Java application inside a JVM comes with startup and footprint costs. GraalVM has a feature to create native images for existing Java applications enabling faster startup and utilizing low memory footprint.
The image generation process employs static analysis to find any code reachable from the main Java method and then performs full ahead-of-time (AOT) compilation. The resulting native binary contains the whole program in machine code form for its immediate execution.
For more deep drive understanding, jump to www.graalvm.org
Installing GraalVM
GraalVM
is available as GraalVM Community and GraalVM Enterprise editions.
For the scope of this article, we shall see on installing GraalVM Community on Ubuntu.
π Refer to Configure Java development environment on Ubuntu 19.10 for detailed steps on installing Ubuntu 19.10
along side with Windows 10
and setup your environment for Java Development
.
Follow below series of steps to install GraalVM 20.2.0 for Java 11:
- Install libz which is needed for building graalvm native image
$ sudo apt-get update -y
$ sudo apt-get install -y libz-dev
- Download GraalVM JDK11
$ cd /home/<user>/Downloads
$ wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.2.0/graalvm-ce-java11-linux-amd64-20.2.0.tar.gz
$ tar -xvzf graalvm-ce-java11-linux-amd64-20.2.0.tar.gz
- Move the unpacked dir to /usr/lib/jvm/ and create a symbolic link to make your life easier when updating the GraalVM version
$ sudo mv graalvm-ce-java11-20.2.0/ /usr/lib/jvm/
- Install the GraalVM Java
$ sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/bin/java 1
- Verify by checking the version number
$ java -version
- Set Path by adding below exports to anywhere above end of the file. Restart the terminal.
$ vi ~/.bashrc
export JAVA_HOME=/usr/lib/jvm/
export PATH=${PATH}:${JAVA_HOME}/bin
export GRAALVM_HOME=/usr/lib/jvm/
- Download Native image installer component package
$ cd /home/<user>/Downloads
$ wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.2.0/native-image-installable-svm-java11-linux-amd64-20.2.0.jar
- Install Native Image using GraalVM Updater
$ gu -L install native-image-installable-svm-java11-linux-amd64-20.0.0.jar
- Verify if native-image is installed
$ gu list
- Test native image
$ mkdir /home/<user>/learning/java
$ vi HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
$ javac HelloWorld.java
$ native-image HelloWorld
$ ./helloworld
π π You are all set if you see Hello, World!
printed in the console. π π
Changes needed to Spring Boot application to support GraalVM
As mentioned above, this article is an extension to the previously published article - A Step by Step guide to create Reactive CRUD RESTful APIs using Spring Boot + Spring Data R2DBC with H2 in-memory database.
The core application remains the same with minor changes to support building native executable using GraalVM.
Uplifting Libraries
Compared to the previous article, libraries are uplifted to their latest versions for better support with GraalVM 20.2.0
- Spring Boot updated from
2.3.3.RELEASE
to2.4.0-M2
. - Added
spring-r2dbc
module coexisting withspring-data-r2dbc
.
Initializing database with spring-r2dbc
In favor of Spring R2DBC
module introduced as part of Spring Framework v5.3.0-M2
, changes are done to Spring Data R2DBC v1.2
to deprecate classes from org.springframework.data.r2dbc.connectionfactory.init
package which are used for initializing database connection.
Rather, we should be using classes from Spring R2DBC
under package org.springframework.r2dbc.connection.init
to initialize io.r2dbc.spi.ConnectionFactory
as below.
package com.toomuch2learn.reactive.nativebuild.catalogue.crud.configuration;
import io.r2dbc.spi.ConnectionFactory;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.r2dbc.connection.init.CompositeDatabasePopulator;import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
/**
* Class to initialize any configurations or beans needed for application
*
* @author Madan Narra
*/
@Configuration(proxyBeanMethods=false)
public class ApplicationConfiguration {
/**
* When using R2DBC, there is no support in Spring Boot to for initialising a database using schema.sql or data.sql.
*
* Database cannot be initialized with schema or seed data by annotating the configuration class with
* @EnableAutoConfiguration or by specifying initialization-mode config param.
*
* @param connectionFactory
* @return connectionFactoryInitializer
*/
@Bean
public ConnectionFactoryInitializer databaseInitializer(ConnectionFactory connectionFactory) {
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer(); initializer.setConnectionFactory(connectionFactory);
CompositeDatabasePopulator populator = new CompositeDatabasePopulator(); populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema/schema.sql"))); populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema/data.sql")));
initializer.setDatabasePopulator(populator);
return initializer;
}
}
Configuring Spring GraalVM Native project
Spring GraalVM Native a.k.a spring-graalvm-native
is an experimental project introduced to support building Spring Boot native applications using GraalVM.
GraalVM Native Image Builder a.k.a native-image
is a utility that processes all the classes of our application and their dependencies, including those from the JDK. It analyses these classes to determine which classes
, methods
and fields
are reachable during application execution. It then ahead-of-time compiles all reachable code and data into a native executable for a specific operating system and architecture. This entire process is called image build time to clearly distinguish it from the compilation of Java source code to bytecode.
When creating the image using native-image
building tool, spring-graalvm-native
will source the information about the application, for example what resources are being loaded, what types might be getting reflected upon and whether types can be safely initialized as the image is built or must be initialized later at runtime. This information enables the native-image
tool to try and build an optimal image for the application.
Add the below dependency to pom.xml to start take advantage of analyzing the application and support building native executable.
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-graalvm-native</artifactId>
<version>0.8.0</version>
</dependency>
Marking proxyBeanMethods=false
The only kind of proxy allowed with native images
is a JDK proxy
. It is not possible to use CGLIB
or some other kind of generated proxy. To compile the application to a native executable, our application needs to switch to using proxyBeanMethods=false
on @SpringBootApplication
and @Configuration
.
proxyBeanMethods
specifies whether @Bean
methods should get proxied in order to enforce bean lifecycle behavior, e.g. to return shared singleton bean instances even in case of direct @Bean method calls in user code.
The default is true
meaning each @Bean
method will get proxied through CgLib
. Each call to the method will pass through the proxy and assuming singleton scoped beans, it will return the same instance each time the method is called.
When setting it to false
no such proxy method will be created and each call to the method will create a new instance of the bean. It will act just as a factory method.
proxyBeanMethods=false
is marked in the below classes which are annotated with @SpringBootApplication
and @Configuration
.
@SpringBootApplication(proxyBeanMethods=false)@EnableConfigurationProperties({
FileStorageProperties.class
})
public class SpringReactiveNativeCatalogueCrudApplication {
public static void main(String[] args) {
SpringApplication.run(SpringReactiveNativeCatalogueCrudApplication.class, args);
}
}
@Configuration(proxyBeanMethods=false)public class ApplicationConfiguration {
@Bean
public ConnectionFactoryInitializer databaseInitializer(ConnectionFactory connectionFactory) {}
}
@Data
@Configuration(proxyBeanMethods = false)@ConfigurationProperties(prefix = "file")
public class FileStorageProperties {
private String uploadLocation;
}
Generating GraalVM Native Image Configurations
To successfully generate a native executable, native-image
builder needs couple of configuration files to register proxies, reflections,resources.
- reflect-config.json
- resource-config.json
- proxy-config.json
- jni-config.json
Having these hand written from scratch is near impossible. To the rescue comes the native-image-agent
tracing agent that produces these configuration files by tracing the classes for their names, methods and types when ran in the Java Hotspot VM.
native-image
builder will automatically picks up configuration files from META-INF/native-image
folder.
So, its ideal to create folder META-INF/native-image
under resources
and have these files generated when the application is launched with the tracing agent.
Application has to be thoroughly exercised by executing all possible use cases, so every nook and corner of the application classes are analyzed and included in the configuration classes.
$ mvn -DskipTests=true clean package
$ export MI=src/main/resources/META-INF
$ mkdir -p $MI
$ java \
-agentlib:native-image-agent=config-output-dir=${MI}/native-image \
-jar target/spring-reactive-native-catalogue-crud-0.0.1-SNAPSHOT.jar
This option is certainly quick but rather manual/tedious.
The other way is to run application tests to exercise the application. This option sounds much more appealing for a robust/repeatable setup but by default the generated configuration will include anything required by the test infrastructure, which is unnecessary when the application runs for real.
To address this problem the agent supports an access-filter.json
file that will cause certain data to be excluded from the generated output.
Include the below to resources\access-filter.json
and thus classes under these registered packages will not be included in the native executable.
{ "rules": [
{"excludeClasses": "org.apache.maven.surefire.**"},
{"excludeClasses": "net.bytebuddy.**"},
{"excludeClasses": "org.apiguardian.**"},
{"excludeClasses": "org.junit.**"},
{"excludeClasses": "org.mockito.**"},
{"excludeClasses": "org.springframework.test.**"},
{"excludeClasses": "org.springframework.boot.test.**"},
{"excludeClasses": "reactor.test.**"},
{"excludeClasses": "com.toomuch2learn.reactive.nativebuild.catalogue.crud.test.**"}
]
}
Updating Maven and Gradle files
As mentioned earlier, couple of dependencies are uplifted to their latest version and few new dependencies are added to support creating native executable.
Maven
Apart from the dependencies uplift, couple of changes need to be done to pom.xml
native
profile is introduced with all applicable plugins registered and to be used as part of native executable build process.- Configure plugin
maven-surefire-plugin
with tracing agent arguments which will be tagged when executing test classes. - New plugin
maven-antrun-plugin
to createMETA_INF\native-image
folder to hold the configuration files created by the tracing agent. - New plugin
native-image-maven-plugin
is to be configured which will help to create the native executable using thenative-image
configuration files - Configure plugin
spring-boot-maven-plugin
with native-image build arguments
Below is the complete pom.xml defined with all the dependencies and plugins needed for this application.
Have close eye at the highlighted code which might need some special interest.
Gradle
π Unfortunatly, there is no Gradle support for this article at the moment as v2.4.0.M2 of βorg.springframework.bootβ is not yet available at plugins.gradle.org.
Section will be uplifted post gradle native-image plugin is available.
Packaging and Running Native Executable Reactive RESTful API Application
Below are series of steps that needs to be executed to build and package the application to run in JVM and Native mode.
JVM Mode
JVM Mode is the regular version building the fat executable jar which can be deployed and executed on different architecture and platform.
$ mvn clean package
$ java -jar target/spring-reactive-native-catalogue-crud-0.0.1-SNAPSHOT.jar
Section will be uplifted post gradle native-image plugin is available.
Native Mode
Native Mode version is very specific to the architecture and platform on which the native executable build is created.
For GraalVM native-image
to build the native executable file, we need to pass certain build arguments which enables us to create the native executable file and also startup the application successfully.
--no-server
--no-fallback
--allow-incomplete-classpath
--report-unsupported-elements-at-runtime
-H:+ReportExceptionStackTraces
-H:+TraceClassInitialization
-Dspring.native.verify=true
-Dspring.graal.mode=initialization-only
-Dspring.graal.dump-config=/tmp/computed-reflect-config.json
-Dspring.graal.verbose=true
-Dspring.graal.skip-logback=true
--initialize-at-build-time=javax.el.MapELResolver
--initialize-at-build-time=javax.el.ListELResolver
--initialize-at-build-time=sun.instrument.InstrumentationImpl
--initialize-at-build-time=io.r2dbc.spi.IsolationLevel,io.r2dbc.spi
--initialize-at-build-time=io.r2dbc.spi.ConstantPool,io.r2dbc.spi.Assert,io.r2dbc.spi.ValidationDepth
--initialize-at-build-time=org.reactivestreams.Publisher
--initialize-at-build-time=com.toomuch2learn.reactive.nativebuild.catalogue.crud.crud.SpringReactiveNativeCatalogueCrudApplication
--initialize-at-build-time=org.springframework.data.r2dbc.connectionfactory
--initialize-at-run-time=org.springframework.data.r2dbc.connectionfactory.ConnectionFactoryUtils
--initialize-at-run-time=io.netty.channel.unix.Socket
--initialize-at-run-time=io.netty.channel.unix.IovArray
--initialize-at-run-time=io.netty.channel.epoll.EpollEventLoop
--initialize-at-run-time=io.netty.channel.unix.Errors
π Refer to Native Image Options for further detailed understanding on the options that can be passed to native-image
command line tool.
π Refer to Class Initialization in GraalVM Native Image Generation for further detailed understanding on difference between initialize-at-build-time
& initialize-at-run-time
.
$ mvn -Pnative clean package
$ ./target/spring-reactive-native-app
Section will be uplifted post gradle native-image plugin is available.
Comparing startup time between JVM & Native mode
As observed in the above images with highlighted blocks, application is booted up and running in 5.286 Secs
in JVM
mode and in 0.528 Secs
in Native
mode.
π± π± π± That is around 90%
decrease in startup times π β¨ π₯
This definitely proves to run Java applications FASTER with native executable.
Containerizing the application
There are two possible approaches to containerize the application with the native executable build:
Using Spring Boot Plugin which uses Buildpacks under the hood
Using a custom Dockerfile
Underlying schematics of the base layer might differ, but the way the application boots and startup remains the same irrespective of the approach.
Create Docker Image Using Spring Boot Plugin
Spring Boot 2.3.0.M1
introduced simplified approach of creating docker images which uses Buildpacks under the hood.
Run the below commands to create the docker image with the native executable build.
$ mvn clean spring-boot:build-image
Section will be uplifted post gradle native-image plugin is available.
π For more details, please refer to Spring Blog
Creating Docker Image from Dockerfile
Custom Dockerfile
is included in src/main/docker
which can be used as an alternative to create docker image with the native executable build.
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1
WORKDIR /work/
COPY target/spring-reactive-native-app /work/application
# set up permissions for user `1001`
RUN chmod 775 /work /work/application \
&& chown -R 1001 /work \
&& chmod -R "g+rwX" /work \
&& chown -R 1001:root /work
EXPOSE 8080USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
$ mvn -Pnative clean package
Section will be uplifted post gradle native-image plugin is available.
$ docker build -f src/main/docker/Dockerfile.native -t spring-reactive-native-app .
Run the Docker Image
Either the way, docker image will be created with spring-reactive-native-app
name. But with the difference in the size of image and the timestamp when the image is created.
$ docker images
spring-reactive-native-app latest c84143c27e3b 40 years ago 142MB
β 40 Years when created with Spring Boot Plugin ?
This is an intentional feature for the purpose of reproducibility. Dig into Reproducible Builds for more information.
$ docker images
spring-reactive-native-app latest ef4d95e79361 25 seconds ago 347MB
$ docker run -it -p8080:8080 spring-reactive-native-app
Gotchas
Native Executable failing with No serializer found for class error
This might be a very common one when getting started with GraalVM Native Executable builds.
If you are ever encountered with the below exception, then Run the application with native-image-agent connected and execute the complete integration tests.
This will add all the applicable classes in reflect-config.json
and other native-image
configuration files.
org.springframework.core.codec.CodecException: Type definition error: [simple type, class com.toomuch2learn.reactive.nativebuild.catalogue.crud.model.CatalogueItem];
nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `com.toomuch2learn.reactive.nativebuild.catalogue.crud.model.CatalogueItem` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (io.netty.buffer.ByteBufInputStream); line: 2, column: 2]
at org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:211) ~[na:na]
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `com.toomuch2learn.reactive.nativebuild.catalogue.crud.model.CatalogueItem` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (io.netty.buffer.ByteBufInputStream); line: 2, column: 2]
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1615) ~[na:na]
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400) ~[na:na]
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1077) ~[na:na]
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1332) ~[na:na]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331) ~[na:na]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164) ~[na:na]
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2079) ~[na:na]
org.springframework.beans.factory.BeanDefinitionStoreException:
I/O failure during classpath scanning; nested exception is java.io.FileNotFoundException: class path resource [com/toomuch2learn/reactive/nativebuild/catalogue/crud/configuration/FileStorageProperties.class] cannot be opened because it does not exist
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.addCandidateComponentsFromIndex(ClassPathScanningCandidateComponentProvider.java:410) ~[na:na]
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:312) ~[na:na]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:276) ~[na:na]
at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132) ~[na:na]
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:295) ~[na:na]
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:249) ~[na:na]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:206) ~[na:na]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:174) ~[na:na]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:336)
~[com.toomuch2learn.reactive.nativebuild.catalogue.crud.springreactivenativecataloguecrudapplication:na]
Native builds are more memory & CPU intensive
GraalVM-based native build are more memory & CPU intensive than regular pure Java builds.
Below is htop
stats captured when creating native executable. As observed, all the logical CPUs utilization are at their peaks with memory utilization beyond 70% when no other application running in background during the build.
native-image
consumes a lot of RAM, it is recommanded to use machine with at least 16G of RAM to build native executable to supress Out of memory errors.
Conclusion
We should definetly be stunned by the blazing startup speed achieved for Spring based applications when natively compiled with GraalVM.
Integrating with spring-graalvm-native
project has easily ported a spring application to run in Native Mode. This will retain die hard spring fans to stay and enjoy building Native Images with Spring Framework rather than migrating to Cloud Native frameworks such as Quarkus
, Helidon
or Micronaut
.
Though this article is extension of A Step by Step guide to create Reactive CRUD RESTful APIs using Spring Boot + Spring Data R2DBC with H2 in-memory database, It gives us the understanding about the whole ecosystem of implementing, testing and deploying RESTful Microservice that can bootup instantly and integrate with supporting tools to monitor, debug and scale. This is also the base for my future articles
- Performing Load Tests using Gatling / k6.io
- Generate Kubernetes resources dekorate
- Deploying to Kubernetes, OpenShift, AWS, Azure, Google Cloud π± π π π¦ π§ π΄
And many moreβ¦