Spring or Micronaut for my next microservice?

João Paulo Gomes
WAES
Published in
6 min readNov 3, 2022

Introduction

The goal of this article is to compare Spring and Micronaut Framework when creating a simple web service that exposes some REST APIs, needs to write and read from a relational database, and also needs to communicate with external services. Besides that, we will see why Micronaut has a better performance and understand how big is the learning curve if you already know Spring.

Spring Framework

Spring Framework is the most used in the Java ecosystem. It was released in 2003 and brought features to make it easier to create web applications.

One of the nice things was the convention over the configuration principle. Instead of configuring dozens of XML files, you can follow a convention adopted by the framework, and it takes care of all the configuration for you. Most of the time, you just need to add an annotation in a class.

With many years since the first version, Spring Framework is robust and complete. It has many functionalities that have been running in production for quite a while. It brings reliability to the developers. Also, Spring has a big community, and it’s easy to find content about the framework.

Micronaut Framework

Released in October of 2018, in the microservices and cloud-native era, Micronaut Framework focused on bringing high performance and low memory usage, making it ideal for building microservices that run on Docker images in Kubernetes clusters. It also has a lower startup time, being good to be used in serverless applications.

Because it’s a new framework, it was designed to be compatible with an interesting feature of the GraalVM: AOT compilation. In the ahead-of-time compilation, the byte codes of your application will be compiled to native code, making it much more interesting to be used in serverless applications because the startup time is almost instantaneous. But, of course, everything in programming is about trade-offs, it has a cost, and the build time is very slow when compared to the byte code compilation. A simple “Hello World” application took almost 5 minutes to compile in an i5 MacBook Pro with 16GB of RAM.

Supported languages

Both frameworks can be used with Java, Kotlin, and Groovy programming languages. Micronaut might support Scala at any moment. The feature is mapped in the project roadmap. The language used in the examples of this article is Kotlin.

Features comparison

So let’s explore some features of both frameworks to see how different they are or not.

Exposing a REST API

If you are familiar with Spring Framework, you know that to create a REST API you just need to create a class with some annotations. Let’s see an example:

@RestController // 1
class PersonController(
private val repository: PersonRepository
) {

@GetMapping("/people/{id}") // 2
fun getById(@PathVariable id: Long): Person {
return repository.findById(id)
}

@GetMapping("/people")
fun listAll(): List<Person> {
return repository.listAll()
}

@PostMapping("/people") // 3
fun add(person: Person): Person {
return repository.add(person)
}

@PutMapping("/people/{id}")
fun replace(@PathVariable id: Long, person: Person): Person {
return repository.update(id, person)
}

@DeleteMapping("/people/{id}")
fun delete(@PathVariable id: Long): Person {
return repository.delete(id)
}
}
data class Person(
val id: Long,
val name: String
)

This is a very simple controller. We are exposing some APIs to do a basic CRUD in an entity called “person”. We can see some annotation in the lines commented with the numbers 1, 2, and 3:

  1. The annotation RestController tells Spring Framework that this class is a controller that returns JSON.
  2. The GetMapping annotation tells Spring that this method should receive HTTP requests in a specific path.
  3. Here, instead of the HTTP GET method, we use the POST method because we want to create a new person.

How to achieve the same result with Micronaut Framework?

@Controller // 1
class PersonController(
private val repository: PersonRepository
) {

@Get("/people/{id}") // 2
fun getById(@PathVariable id: Long): Person {
return repository.findById(id)
}

@Get("/people")
fun listAll(): List<Person> {
return repository.listAll()
}

@Post("/people") // 3
fun add(person: Person): Person {
return repository.add(person)
}

@Put("/people/{id}")
fun replace(@PathVariable id: Long, person: Person): Person {
return repository.update(id, person)
}

@Delete("/people/{id}")
fun delete(@PathVariable id: Long): Person {
return repository.delete(id)
}
}

@Introspected
data class Person(
val id: Long,
val name: String
)

It looks the same, right? Well, with a few differences in the annotation names. Instead of using @RestController, we use @Controller. Instead of @GetMapping, @PostMapping, we use @Get and @Post. But we have a new annotation in the Person data class.

What is the @Introspected annotation?

Since version 1.1 Micronaut Framework has a replacement for the Java Reflection. The main difference is that the Micronaut version works at compilation time, and to minimize the amount of work that the compiler needs to do to add the metadata in the classes, we need to inform which classes should be available for introspection.

This is the main reason why a Micronaut application uses less memory and starts faster than a Spring application. But, if you want, you can still use Java Reflection in your application. You just need to be aware that it will affect your application performance.

Consuming a REST API

Imagine consuming an API from a third-party service to get an address from a postal code. Let’s see one way to do that with Spring:

@Service
class PostalCodeClient{
fun find(postalCode: String) : Mono<PostalCodeResponse> {
return WebClient
.create("https://postalcode.service/v1/postal-codes?code=$postalCode")
.get()
.retrieve()
.bodyToMono(PostalCodeResponse::class.java)

}
}

This example uses the Spring WebClient from the Spring WebFlux dependency. It’s a non-blocking way of doing an HTTP call. With Micronaut, we can achieve the same result using an interface with some annotations. The interface will be implemented at compilation time.

@Client("https://postalcode.service/v1")
interface PostalCodeFinder {

@Get("/postal-codes")
fun find(@QueryValue postalCode: String): Publisher<PostalCodeResponse>
}

If the declarative way it’s not enough for your use case, you can also do it imperatively:

@Singleton
class PostalCodeFinderLowLevel(
@Client("https://postalcode.service/v1") private val httpClient: HttpClient
) {
fun find(postalCode: String): Publisher<PostalCodeResponse> {
val req = HttpRequest.GET<Any>("/postal-codes?postalCode=$postalCode")
return httpClient.retrieve(req, PostalCodeResponse::class.java)
}
}

Interacting with relational databases

Spring Framework has a nice declarative ORM. Instead of writing SQL queries, you can write your function name using a specific convention, and the framework will do the rest for you. Let’s see an example:

@Repository
interface PersonRepository : CrudRepository<Person, String> {
fun findByUsername(username: String): Optional<Person>
}

This repository is going to find a person by its unique identifier called username. Micronaut Framework also has an ORM much similar, and the code above works for Spring and Micronaut. Despite the code being the same, there is a huge difference in how it works and its performance.

Micronaut Data is just a thin layer. The framework will implement this interface at compilation time and will also implement all your SQL queries. So, if you are referencing a non-existing field in a table, for example, your build will fail. This way, you don’t have bad surprises at runtime.

Conclusion

As we can see from the code examples, Micronaut has many similarities with Spring, the most visible one in the usage of annotations. Because of this similar way of doing things, the learning curve to learn Micronaut, if you already know Spring, is small.

We also saw why Micronaut has a better performance than Spring. If you want to see some numbers, you can check this performance comparison between Spring and Micronaut and this post about Micronaut Data performance.

Micronaut has a better startup time, the jar size is smaller, and it will use less memory during runtime. On the other hand, Spring has a bigger community and much more features out of the box. It can save development time and help you go faster to production.

So, which one are you going to use in your next project?

Do you think you have what it takes to be one of us?

At WAES, we are always looking for the best developers and data engineers to help Dutch companies succeed. If you are interested in becoming a part of our team and moving to The Netherlands, look at our open positions here.

WAES publication

Our content creators constantly create new articles about software development, lifestyle, and WAES. So make sure to follow us on Medium to learn more.

Also, make sure to follow us on our social media:
LinkedInInstagramTwitterYouTube

--

--

João Paulo Gomes
WAES
Writer for

Hi! I’m JP! I work as a Kotlin and Java developer and in my spare time I like to cook. My github https://github.com/johnowl