Issues with Spring, how Micronaut solves it, and latter’s support for GraalVM

Shivam Garg
Dev Genius
Published in
7 min readMay 15, 2021

--

JAVA has a very robust and mature ecosystem, but most of the JAVA developers are at times bombarded with questions regarding Java memory consumption and startup times.

I already discussed this aspect for a Spring Boot application and how GraalVM provides stunning groundbreaking improvements in this regard in Part 1 of this article

But the question still lingers …

Can we perform better?
Are there some aspects in the Spring framework itself which contribute to this issue?
Is there a framework that already does things better?

Well, we will try to find out answers to these by deep diving into -

  • Micronaut framework and how is it different from Spring Boot
  • Create a REST application similar to the one we did in Part 1
  • Convert the application to Native Image using GraalVM

So, fasten your seatbelts, and let’s start 😉

Drawbacks of Spring

Well, we all know that Spring is an awesome framework for developing microservices. It is extremely powerful in terms of DI (Dependency Injection) and AOP (Aspect Oriented Programming).

But…… IT DOES THESE ALL AT RUNTIME !!!!

So, what’s the issue with runtime? Well, it consumes a whole lot of memory.
Spring’s runtime reflection approach leads to longer startup times as well.

How is Micronaut different?

Now, the question that remains is that —

“How can we use all the goodness of Spring and its productivity benefits, while at the same time keeping memory in check?”

Well, the answer is Micronaut. It makes up for the performance issues by heavily reducing the startup times and reducing the RAM consumption as well. And, this is what opens the door to the true serverless world.

So, how does it exactly achieve this? The answer is COMPILATION !!!

Micronaut uses Ahead of time compilation (AOT) to collect the necessary information at compile time. and perform —

  • Dependency and Configuration injection (DI)
  • AOP Proxies
  • Precompute Annotation Metadata

In other words, it provides the goodness of Spring, but reflection-free as much as possible. And hence, it avoids paying the cost of more memory and longer startups.
It definitely increases the compile-time, but it is a small cost paid for much more goodness !!!

Go through this amazing talk by Micronaut’s founder Graeme Rocher for more info —

Sample Micronaut Application

Let’s code a Micronaut application now. It will be based exactly on the Spring Boot application we coded in Part 1.
The application will expose two REST endpoints, both of which will accept HTTP GET requests.

  • The first endpoint should provide the information corresponding to a Github user ( /users/{githubUserName} ).
  • The second endpoint should provide the information corresponding to the contributors of a Github repository
    ( /contributors/{githubOrgName}/{githubRepoName} ).

The GithubController can be defined as follows —

We use @ Controller annotation in Micronaut for writing Rest Controllers.
HttpResponse is Micronaut’s equivalent to Spring Boot’s ResponseEntity .

We use GithubClient class to fetch the user or contributor details.

Here, we use Micronaut’s inbuilt reactive non-blocking HTTP client — RxHttpClient . We inject it using @ Client annotation.
Note that @ Singleton annotation is used at top of the class to state that a singleton bean must be created.
Also, note that toBlocking() is used as RestTemplate we used in Part 1 in Spring Boot is a blocking HTTP Client.

Now, let’s have a look at the User DTO class, which will be used as the REST Response.

Note carefully that @ Introspected annotation is used here. It enables you to do reflection-free bean introspection. This helps in marshaling/unmarshaling JSON without using reflection.

The Github API imposes a rate limit, owing to which you can’t make more than 60 requests in an hour from a given IP. We can increase this rate limit using Github authentication tokens.

Let’s define GithubProperties as the Micronaut ConfigurationProperties .

So, now we can add a property named github.token in the application.properties / application.yaml .

Now, how to use this token when trying to invoke the Github API? Well, we need to implement the HttpClientFilter . This works the same as the RestTemplate interceptors we defined in Part 1.

Here, the doFilter function of GithubAppTokenFilter adds an Authorization request header with the encoded value of the token provided.
Since @ Valid and @ Pattern annotations are used — it will be ensured that the property follows the mentioned regular expression.

The doFilter function of the RateLimitHeaderFilter extracts the
X-RateLimit-Remaining response header and provides a log statement to provide the information regarding the rate limit remaining.

The final piece of the code is the entry point of the application.

The code which we discussed so far can be found here —

Now, build using mvn clean install .
Build time for the Spring Boot application is 2.6 sec , whereas Micronaut takes obviously longer — 4.3 sec .

Then run mvn mn:run command and the application will start at port 8080 .

So, the startup on my machine takes around 600 ms , and memory consumption was around 155 MB , which is a much better result than what we saw in the Spring Boot application.

As the load increases, the Micronaut application consumes way lesser memory. The requests/sec were also quite better.

You can have a look at the in-depth comparison between these two here based on a multitude of metrics —

Mirconaut ❤️ GraalVM

You must have already checked out in Part 1 that how GraalVM massively improves startup time and memory consumption using AOT to form a native image.

GraalVM does all this using static analysis, and since Micronaut eliminates Reflection, Runtime Proxies, Dynamic Class Loading, and Byte Code generation — it works extremely well out of the box with GraalVM.

Let’s convert the same application into a native image now.

Unlike in Part 1, where we had to use Spring Native, in this case — the GraalVM native-image-maven-plugin already comes handy in the
micronaut-parent maven dependency.

So, for building with AOT, you can build the module simply by using this command-
mvn package -Dpackaging=native-image

This will create a native executable comprising the Micronaut application under the target folder of the module.
Simply invoke — target/rest-service .

If you see now, the startup time on my machine was 21 ms😳 😯

Lightning-fast !!!

Also, the memory consumption this time should be around 30 MB compared to 155 MB in the JIT variant.

This clearly proves that GraalVM + Micronaut is a deadly combination. The low RAM consumption shows how valuable the approach can be for the serverless world, where every megabyte of RAM costs money.

Conclusion

The Micronaut framework provides all the goodness of Spring without having to sacrifice memory and startup time.

So, should we just discard Spring and start using Micronaut? 🤔

Well, overtaking Spring is a herculean task, to be honest. There are many fans and users of Spring (including me 😂), and switching over to Micronaut is not an easy choice to make.

Micronaut is still in an emerging state and is proving its mettle. It is just a matter of time before it gains the huge recognition it deserves. And with GraalVM, it is an absolute beast!!!

Competition is always good for the industry. Maybe, few of Micronaut’s design improvements find their way into the Spring framework 😁

We will discuss the advanced configuration setups and some corner cases of GraalVM in the next part of this post.

You can express your opinions and give suggestions in the comments.

Thanks 😊

--

--

Software Developer at Nutanix. In love with Spring Boot, Spring Cloud and Micronaut