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:

$ sudo apt-get update -y

$ sudo apt-get install -y libz-dev
$ 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
$ sudo mv graalvm-ce-java11-20.2.0/ /usr/lib/jvm/
$ sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/bin/java 1
$ java -version  
$ vi ~/.bashrc

export JAVA_HOME=/usr/lib/jvm/
export PATH=${PATH}:${JAVA_HOME}/bin

export GRAALVM_HOME=/usr/lib/jvm/
$ 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
$ gu -L install native-image-installable-svm-java11-linux-amd64-20.0.0.jar
$ gu list
$ 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

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.

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

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:

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

And many more…

native-reactive-crud-rest-api-using-spring-boot-spring-r2dbc