CRUD from Scratch

A Step by Step guide to create Native Executable Reactive CRUD RESTful APIs using GraalVM + Spring Boot + Spring R2DBC with H2 in-memory database

Clone the source code of the article from native-reactive-crud-rest-api-using-spring-boot-spring-r2dbc

Introduction

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

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 to 2.4.0-M2.
  • Added spring-r2dbc module coexisting with spring-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.

Initializing R2DBC Connection using spring-r2dbc module
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.

pom.xml
<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.

SpringReactiveNativeCatalogueCrudApplication.java
@SpringBootApplication(proxyBeanMethods=false)@EnableConfigurationProperties({
	FileStorageProperties.class
})
public class SpringReactiveNativeCatalogueCrudApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringReactiveNativeCatalogueCrudApplication.class, args);
	}

}
ApplicationConfiguration.java
@Configuration(proxyBeanMethods=false)public class ApplicationConfiguration {

  @Bean
  public ConnectionFactoryInitializer databaseInitializer(ConnectionFactory connectionFactory) {}

}
FileStorageProperties.java
@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.

Follow below steps to launch the application with tracing
$ 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.

access-filter.json
{ "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 create META_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 the native-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.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.0-M2</version>		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.toomuch2learn</groupId>
	<artifactId>spring-reactive-native-catalogue-crud</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-reactive-native-catalogue-crud</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>11</java.version>
		<start-class>
            com.toomuch2learn.reactive.nativebuild.catalogue.crud.SpringReactiveNativeCatalogueCrudApplication        </start-class>
		<io.r2dbc.version>0.8.3.RELEASE</io.r2dbc.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>
		<dependency>			<groupId>org.springframework.boot</groupId>			<artifactId>spring-boot-starter-data-r2dbc</artifactId>		</dependency>		<dependency>			<groupId>org.springframework</groupId>			<artifactId>spring-r2dbc</artifactId>		</dependency>
		<!--
			https://stackoverflow.com/questions/62358435/graalvm-native-image-for-springboot-fat-jar-throws-nosuchmethodexception-xxx-in
			https://stackoverflow.com/questions/50911552/graalvm-and-spring-applications/61680146#61680146
		-->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-indexer</artifactId>
		</dependency>
		<dependency>
			<groupId>io.r2dbc</groupId>
			<artifactId>r2dbc-spi</artifactId>
		</dependency>
		<dependency>
			<groupId>io.r2dbc</groupId>
			<artifactId>r2dbc-pool</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>io.r2dbc</groupId>
			<artifactId>r2dbc-h2</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>provided</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>compile</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.yaml</groupId>
			<artifactId>snakeyaml</artifactId>
			<version>1.25</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.experimental</groupId>
			<artifactId>spring-graalvm-native</artifactId>
			<version>0.8.0</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>				<groupId>org.springframework.boot</groupId>				<artifactId>spring-boot-maven-plugin</artifactId>				<configuration>					<image>						<name>spring-reactive-native-app</name>						<builder>paketobuildpacks/builder:tiny</builder>						<env>							<BP_BOOT_NATIVE_IMAGE>1</BP_BOOT_NATIVE_IMAGE>							<BP_BOOT_NATIVE_IMAGE_BUILD_ARGUMENTS>								--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							</BP_BOOT_NATIVE_IMAGE_BUILD_ARGUMENTS>						</env>					</image>				</configuration>			</plugin>		</plugins>
	</build>

	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</repository>
		<repository>
			<id>Maven Repo</id>
			<name>Maven Repo</name>
			<url>https://repo1.maven.org/maven2/</url>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</pluginRepository>
	</pluginRepositories>

	<profiles>
		<profile>
			<id>native</id>			<build>
				<plugins>					<plugin>						<artifactId>maven-antrun-plugin</artifactId>						<executions>							<execution>								<id>make-native-image-config-folder</id>								<phase>test-compile</phase>								<configuration>									<target>										<mkdir dir="src/main/resources/META-INF/native-image"/>									</target>								</configuration>								<goals>									<goal>run</goal>								</goals>							</execution>						</executions>					</plugin>					<plugin>						<groupId>org.apache.maven.plugins</groupId>						<artifactId>maven-surefire-plugin</artifactId>						<configuration>							<argLine>-Dorg.graalvm.nativeimage.imagecode=agent -Dspring.xml.ignore=true -Dspring.spel.ignore=true -agentlib:native-image-agent=access-filter-file=target/classes/access-filter.json,config-output-dir=src/main/resources/META-INF/native-image</argLine>						</configuration>					</plugin>					<plugin>						<groupId>org.graalvm.nativeimage</groupId>						<artifactId>native-image-maven-plugin</artifactId>						<version>20.2.0</version>						<configuration>							<mainClass>com.toomuch2learn.reactive.nativebuild.catalogue.crud.SpringReactiveNativeCatalogueCrudApplication</mainClass>							<imageName>spring-reactive-native-app</imageName>							<buildArgs>								--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							</buildArgs>						</configuration>						<executions>							<execution>								<goals>									<goal>native-image</goal>								</goals>								<phase>package</phase>							</execution>						</executions>					</plugin>					<plugin>						<groupId>org.springframework.boot</groupId>						<artifactId>spring-boot-maven-plugin</artifactId>					</plugin>				</plugins>			</build>
		</profile>
	</profiles>

</project>

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.

Gradle
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.

Maven
$ mvn clean package

$ java -jar target/spring-reactive-native-catalogue-crud-0.0.1-SNAPSHOT.jar
Gradle
Section will be uplifted post gradle native-image plugin is available.

JVM mode Startup
JVM mode startup time in 5.289 seconds

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.

Build arguments passed to native-image command line tool
--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.

Maven
$ mvn -Pnative clean package

$ ./target/spring-reactive-native-app
Gradle
Section will be uplifted post gradle native-image plugin is available.

Native mode Startup
Native mode startup time in 0.528 seconds

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.

maven
$ mvn clean spring-boot:build-image
gradle
Section will be uplifted post gradle native-image plugin is available.

Spring Boot Plugin Docker Image
String Boot Plugin took 15 mins to build and package Docker Image

πŸ‘‰ 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.

Dockerfile.native
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"]
maven
$ mvn -Pnative clean package
gradle
Section will be uplifted post gradle native-image plugin is available.

Spring Boot native executable build
Build took 8.34 mins to create native executable alone

Create Docker Image
$ 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.

Image created with Spring Boot Plugin
$ 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.

Image created with Dockerfile
$ docker images

spring-reactive-native-app   latest  ef4d95e79361  25 seconds ago  347MB
Run Doker Image
$ docker run -it -p8080:8080 spring-reactive-native-app

Docker Image Startup

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.

Deserialization Exception stacktrace
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]
FileNotFound Exception stacktrace
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.

htop stats when building native executable
htop stats when building native executable

htop

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…

Clone the source code of the article from native-reactive-crud-rest-api-using-spring-boot-spring-r2dbc

Madan Narra16 Posts

Software developer, Consultant & Architect

Madan is a software developer, writer, and ex-failed-startup co-founder. He has over 10+ years of experience building scalable and distributed systems using Java, JavaScript, Node.js. He writes about software design and architecture best practices with Java and is especially passionate about Microservices, API Development, Distributed Applications and Frontend Technologies.

  • Github
  • Linkedin
  • Facebook
  • Twitter
  • Instagram

Contents

Get The Best Of All Hands Delivered To Your Inbox

Subscribe to our newsletter and stay updated.