Spring.io has released the experimental version of Spring Native since March, a toolsuite to support the construction of compileable and executable springboot projects on GraalVM.
But first: What is GraalVM and what problem is it supposed to solve?
Background: When you mix containers and JREs.
Since the first container-based deployments became popular, the debate has arisen about whether Java as such might have its days numbered.
In my opinion, Java is too widespread and used in too many areas to be considered a single language. There's simply too much good code written in it.
On the other hand, the Java programming language, and especially the Java virtual machine, is a solution from the mid-90s that came to answer the problem of developing and running software on different platforms.
Basically, JDK lets you do that: It's a program on which your software runs. virtuallyThanks to this advancement, you could disregard the type of hardware or operating system where your software would run. You only needed to have a compatible JRE version installed on your server.
This gain, as we all know, doesn't come without a price. The Java Virtual Machine, being an additional layer between your software and your hardware, incurs a cost in memory and performance. But since there was no better solution, and because it works extremely well, these costs were accepted without question.

But now, well into the 21st century, the paradigm is changing. The container deployment model, among many other things, gives us the power to define using a layered model. A fully virtualized execution environment is another way to address the same problem.

But, What happens if we mix these two concepts? So we found the following:

As you can see, for each service, a heavy virtualization layer (JRE) is added inside the containers on top of Docker's own virtualization layer, significantly and unnecessarily increasing resource consumption.
For all these reasons, in today's highly scalable environments, developing microservices on a "traditional" JDK is not recommended, in favor of other relatively more direct execution platforms such as node.js, python, GoLang, etc.
GraalVM: A solution to the problem?

GraalVM It is a high-performance runtime environment for Java, JavaScript, and LLVM-based languages such as C and C++, among others.
GraalVM primarily acts as a "translator layer" between programming languages or Java object code and your operating system (or, in the case of containers, the next virtualization layer). It also enables preemption of native executables to speed up startup time and reduce memory overhead.
Or, to put it another way, it's a way to run Java applications without needing to start a JRE and that allows you to speed up their startup if you define a configurable class preload.
Because it doesn't follow the standard JDK structure, GrallVM doesn't directly execute any JAR package. In general, GraalVM It seems to have its own packaging and configuration rules, which would warrant another post.
Luckily for us, frameworks like Micronaut already exist (https://micronaut.io/) or Quarkus (https://quarkus.io/) and experimentally Spring Native that abstracts us from all of that.
Okay, GraalVM is great, but why Spring Boot Native?
Short answer: Because of its history, its community, and everything that surrounds it.
Spring Boot, and by extension Spring, is one of the most successful Java development frameworks of all time, boasting one of the longest-standing and most active communities in the Java world. There is a wealth of accumulated expertise on Spring Boot that would be a shame to lose.
On the other hand, Micronaut is a relatively new framework (2018) that has been gaining significant traction in recent years and already boasts a strong supporting community. Its framework is considered more stable, and for deployments on GraalVM, it can be considered the preferred solution.
Also in the running is Quarkus, even more recent (March 2019). Quarkus is designed for running native Java in Kubernetes environments, following the philosophy container-first and, of course, also supported by GraalVM for this.
And although I don't rule out future posts talking about Quarkus and Micronaut, because of the years I've personally been working with Spring and the good results it has given me, Spring Boot Native had to be the first one to be examined.
Pilot construction
As a pilot project, we will set up a typical CRUD service against a PostgreSQL database, although any other database with r2dbc support would work.
We're not going to complicate things, we're going to https://start.spring.io/:

With this configuration, we click on GENERATEAnd now we have a zip file with the starting project.
Adding drivers and persistence
We open pom.xml and add the reactive drivers for PostgreSQL to the dependencies:

Since the Spring Native project is in an experimental state and Spring Data already provides a good foundation for building CRUDs, we will avoid including non-essential libraries like Apache Lombok or Hibernate JPA in this demo, as these could generate unnecessary conflicts.
Now we need to add persistence.
To do this, we used spring-data and created a Book entity, and its respective R2dbcRepository:
Libro.java

LibroRepository.java

We already have persistence, now to set up the services.
Services
We will create two Java classes: LibroServiceImpl.java (and its corresponding interface with business operations) and LibroController.java, responsible for shaping the REST facade of the CRUD.
LibroService.java

LibroServiceImpl.java

LibroController.java

We essentially have all the code copied. We just need a couple of tweaks:
- In the DemoSpringNativeApplication.java class, we added the annotation @EnableR2dbcRepositories.
- We created a PostgreSQL database and added the table for books:

- In the file application.properties, we include the connection properties to said database.

And now we have the source code ready to package.
Packing for GraalVM
The best way to build a Docker image with GraalVM is to use the spring-boot:build-image plugin
$ mvn clean package spring-boot:build-image |
NoteThe packaging process consumes a large amount of memory (officially they recommend 16GB available) as well as a large amount of time (it takes no less than 5 minutes).
Once the process is complete, a message like the following should appear in the console:
| [INFO] Successfully built image ‘docker.io/library/demo-spring-native-graalvm:1.0.0’ |
This means that, in our local Docker, there is a new image with this tag that can be deployed.
$ docker run -p 8080:8080 -e R2DBC_URL=… -e R2DBC_USER=… -e R2DBC_PASS=… docker.io/library/demo-spring-native-graalvm:1.0.0 |
The first thing that will surprise you is the startup time, around 0.12 seconds (If you blink, you'll miss it).
| e.s.r.d.DemoSpringNativeApplication : Started DemoSpringNativeApplication in 0.116 seconds (JVM running for 0.119) |
To test it, you can use Postman or run curl requests manually:
GET
$ curl --location --request GET 'http://localhost:8080/libros/' |
PUT (Insert new element)
$ curl --location --request PUT 'http:// localhost:8080/libros' \ |
POST (Modify an element)
$ curl --location --request POST 'http://localhost:8080/libros' \ |
DELETE
$ curl --location --request DELETE 'http://localhost:8080/libros/9780007141326' |
Comparing Consumption
Now, to be sure, we will first use the same source code to build a container with the "classic" JRE packaging and thus compare the metrics that each one yields.
Furthermore, to make the rules as fair as possible, we're simply going to assemble and launch it on a container based on a very resource-efficient image: registry.access.redhat.com/ubi8/openjdk-11-runtime.
To begin with, the startup time has gone from milliseconds to full seconds: approximately 4-5 seconds.
| e.s.r.d.DemoSpringNativeApplication: Started DemoSpringNativeApplication in 4.057 seconds (JVM running for 4.826) |
For a look at metrics more focused on resource consumption, in my case, since I have Portainer set up locally for other purposes, I'm going to limit myself to comparing graphs.
Container with openjdk-runtime:

Container with GraalVM:

As you can see, RAM usage is considerably less than half. I haven't noticed any performance differences between them, although it's true that since they are small projects and also have reactive services, it's difficult to measure differences without doing real load tests.
In addition, images with GraalVM weigh almost 4 times less (112MB vs 400MB approx).
CONCLUSIONS
Spring Native is a promising start that still has many rough edges to polish.
The construction process is delicate. In my case, for example, I had to reassemble the base project a couple of times.
Not everything is compatible to date. For example, the configuration for Swagger itself, @OpenAPIDefinitionIt had startup problems, although it was possibly due to a lack of some detail in the configuration of the construction process.
Furthermore, as I've already mentioned, the build-image execution consumes a lot of memory (its official documentation recommends 16GB), and it takes too long to complete (~6 minutes in the example). In the context of local development and continuous integration, these are excessive wait times.
In summary, Spring Native is a very beta-stage project that we shouldn't lose track of, although due to the difficulties encountered I don't consider it mature enough to use professionally today.
You can download the source of this article by accessing its GitHub page
At SOLTEL, we have an innovative approach to technology because we are passionate about it. Our extensive experience working with diverse technologies over many years helps us approach projects with a broad perspective and become true technology partners for our clients.
Article written by Ramón Tur Vázquez, Solutions Architect at SOLTEL Group







