Note: this article was originally published on Stack Labs blog.
Until not too long ago, when we wanted to develop JVM microservices, there wasn't much choice beyond the almighty Spring Boot. This is not true anymore with the arrival of frameworks such as Eclipse Microprofile, Helidon, Quarkus or Micronaut. In this article we will focus on Micronaut framework to discover how we can use it to develop microservices through a simple hello-world app example.
Micronaut goal
You might be wondering why using another JVM framework when Spring Boot is already working quite well for us.
If we read Micronaut official documentation introduction, it becomes clear what Micronaut claims to provide:
- Fast startup time
- Reduced memory footprint
- Minimal use of reflection
- Minimal use of proxies
- Easy unit testing
By providing these points Micronaut clearly takes a completely different approach to the one taken by older frameworks like Spring Boot, Spring or Grails, and Micronaut developers know what they are talking about because they are... former Grails framework developers !
Although there are downsides to famous historical frameworks, a lot of things work well (well, that's why frameworks like Spring Boot became so popular, isn't it ?) and Micronaut intends to keep all these well-working things, such as dependency injection, auto-configuration, service discovery and HTTP clients and servers.
Now that we know what Micronaut goal is, let's dive into a real Micronaut application to see concretely how it goes for us developers ⌨️️
Creating a fully-featured app skeleton with Micronaut CLI
All the great frameworks provide a quick way to create a pre-configured app skeleton to enable for a very quick start. The idea behind this being that you spend less time initializing and configuring your app and more time actually coding and adding business value.
If you've ever seen one of Pivotal best speaker Josh Long, you already know "start dot spring dot io" where you can select the various features and other configurations for your brand new app.
Well Micronaut does not provide a web UI but a simple CLI called mn
to allow easy app creation. You can easily install it using Sdkman or directly with Homebrew on a Mac.
Mac install:
> brew update && brew install micronaut
Unix install:
# With Sdkman installed
> sdk update && sdk install micronaut
➡️ Let's see what features Micronaut provides by running the following command:
> mn profile-info service
Resolving dependencies..
| Profile: service
--------------------
The service profile
| Provided Commands:
--------------------
create-bean Creates a singleton bean
create-client Creates a client interface
create-controller Creates a controller and associated test
create-job Creates a job with scheduled method
create-repository Creates a repository and associated test
create-test Creates a simple test for the project's testing framework
create-websocket-client Creates a Websocket client
create-websocket-server Creates a Websocket server
help Prints help information for a specific command
| Provided Features:
| (+) denotes features included by default.
--------------------
annotation-api (+) Adds Java annotation API
application (+) Facilitates creating an executable JVM application and adds support for creating fat/uber JARs
asciidoctor Adds Asciidoctor documentation
aws-api-gateway Adds support for AWS API Gateway
aws-api-gateway-graal Creates an AWS API Gateway Proxy Lambda with Graal Native Image
cassandra Adds support for Cassandra in the application
config-consul Adds support for Distributed Configuration with Consul (https://www.consul.io)
data-hibernate-jpa Adds support for Micronaut Data Hibernate/JPA
data-jdbc Adds support for Micronaut Data JDBC
discovery-consul Adds support for Service Discovery with Consul (https://www.consul.io)
discovery-eureka Adds support for Service Discovery with Eureka
ehcache Adds support for Ehcache (https://www.ehcache.org/)
elasticsearch Adds support for Elasticsearch in the application
file-watch Adds automatic restarts and file watch
flyway Adds support for Flyway database migrations (https://flywaydb.org/)
graal-native-image Allows Building a GraalVM Native Image
graphql Adds support for GraphQL in the application
groovy Creates a Groovy application
hazelcast Adds support for Hazelcast (https://hazelcast.org)
hibernate-gorm Adds support for GORM persistence framework
hibernate-jpa Adds support for Hibernate/JPA
http-client (+) Adds support for creating HTTP clients
http-server (+) Adds support for running a Netty server
java Creates a Java application
jdbc-dbcp Configures SQL DataSource instances using Commons DBCP
jdbc-hikari Configures SQL DataSource instances using Hikari Connection Pool
jdbc-tomcat Configures SQL DataSource instances using Tomcat Connection Pool
jib Adds support for Jib builds
jrebel Adds support for class reloading with JRebel (requires separate JRebel installation)
junit Adds support for the JUnit 5 testing framework
kafka Adds support for Kafka
kafka-streams Adds support for Kafka Streams
kotlin Creates a Kotlin application
kotlintest Adds support for the KotlinTest testing framework
kubernetes Adds support for Kubernetes
liquibase Adds support for Liquibase database migrations (http://www.liquibase.org/)
log4j2 Adds Log4j2 Logging
logback (+) Adds Logback Logging
management Adds support for management endpoints
micrometer Adds support for Micrometer metrics
micrometer-appoptics Adds support for Micrometer metrics (w/ AppOptics reporter)
micrometer-atlas Adds support for Micrometer metrics (w/ Atlas reporter)
micrometer-azure-monitor Adds support for Micrometer metrics (w/ Azure Monitor reporter)
micrometer-cloudwatch Adds support for Micrometer metrics (w/ AWS Cloudwatch reporter)
micrometer-datadog Adds support for Micrometer metrics (w/ Datadog reporter)
micrometer-dynatrace Adds support for Micrometer metrics (w/ Dynatrace reporter)
micrometer-elastic Adds support for Micrometer metrics (w/ Elastic reporter)
micrometer-ganglia Adds support for Micrometer metrics (w/ Ganglia reporter)
micrometer-graphite Adds support for Micrometer metrics (w/ Graphite reporter)
micrometer-humio Adds support for Micrometer metrics (w/ Humio reporter)
micrometer-influx Adds support for Micrometer metrics (w/ Influx reporter)
micrometer-jmx Adds support for Micrometer metrics (w/ Jmx reporter)
micrometer-kairos Adds support for Micrometer metrics (w/ Kairos reporter)
micrometer-new-relic Adds support for Micrometer metrics (w/ New Relic reporter)
micrometer-prometheus Adds support for Micrometer metrics (w/ Prometheus reporter)
micrometer-signalfx Adds support for Micrometer metrics (w/ SignalFx reporter)
micrometer-stackdriver Adds support for Micrometer metrics (w/ Stackdriver reporter)
micrometer-statsd Adds support for Micrometer metrics (w/ Statsd reporter)
micrometer-wavefront Adds support for Micrometer metrics (w/ Wavefront reporter)
mongo-gorm Configures GORM for MongoDB for Groovy applications
mongo-reactive Adds support for the Mongo Reactive Streams Driver
neo4j-bolt Adds support for the Neo4j Bolt Driver
neo4j-gorm Configures GORM for Neo4j for Groovy applications
netflix-archaius Adds support for Netflix Archaius in the application
netflix-hystrix Adds support for Netflix Hystrix in the application
netflix-ribbon Adds support for Netflix Ribbon in the application
picocli Adds support for command line parsing (http://picocli.info)
postgres-reactive Adds support for the Reactive Postgres driver in the application
rabbitmq Adds support for RabbitMQ in the application
redis-lettuce Configures the Lettuce driver for Redis
security-jwt Adds support for JWT (JSON Web Token) based Authentication
security-session Adds support for Session based Authentication
spek Adds support for the Spek testing framework
spock Adds support for the Spock testing framework
springloaded Adds support for class reloading with Spring-Loaded
swagger-groovy Configures Swagger (OpenAPI) Integration for Groovy
swagger-java Configures Swagger (OpenAPI) Integration for Java
swagger-kotlin Configures Swagger (OpenAPI) Integration for Kotlin
tracing-jaeger Adds support for distributed tracing with Jaeger (https://www.jaegertracing.io)
tracing-zipkin Adds support for distributed tracing with Zipkin (https://zipkin.io)
vertx-mysql-client Add support for the Reactive MySQL Client in the application
vertx-pg-client Add support for the Reactive Postgres Client in the application
Great, we can see all the features provided by Micronaut. By the way we can note that some features such as annotation-api
, application
, http-client
, http-server
or logback
are included by default in any application created through mn
command as they are considered basic features for any real-world application 🙂
➡️ Let's now create a hello-world
Java application with a few features (plus features included by default) and with Maven build tool (mn
defaults to Gradle):
> mn create-app com.stacklabs.micronaut.hello-world --lang=java --build=maven --features=junit
| Generating Java project...
| Application created at /Users/path/to/the/micronaut-workshop/hello-world
Great, we now have our app skeleton, time to code !
Note: Micronaut CLI is great for initializing an app and avoiding the creation of Maven/Gradle boilerplate, but it is pretty basic and you cannot use it to add new features to your app once it has been initialized. If you have forgotten features, you will have to add them manually on your existing app build.
Creating a simple controller
The controller
We will now create a controller, but before that take a look at the main class generated for us by mn
command:
public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class);
}
}
Not too complicated right ?
➡️ Now, to create our controller we can use mn
command again:
> mn create-controller Hello
| Rendered template Controller.java to destination src/main/java/fr/stacklabs/HelloController.java
| Rendered template Spec.groovy to destination src/test/groovy/fr/stacklabs/HelloControllerSpec.groovy
As we can see, the command line generated a Java class (because we created a Java app) for our controller and its test counterpart. Let's have a look at this controller:
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.HttpStatus;
@Controller("/hello")
public class HelloController {
@Get("/")
public HttpStatus index() {
return HttpStatus.OK;
}
}
Pretty basic, the default skeleton creates a controller with a simple endpoint returning a HTTP 200
. Note that the @Controller
and @Get
annotations are not JAX-RS annotations but Micronaut specific annotations.
➡️ Let's modify it so it adds "Hello Micronaut !" to our HTTP 200 response body:
@Controller("/hello")
public class HelloController {
@Get("/")
public String index() {
return "Hello Micronaut !";
}
}
The test
➡️ Let's now open the test and check the default content:
import io.micronaut.context.ApplicationContext;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.test.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import javax.inject.Inject;
import static org.junit.jupiter.api.Assertions.assertEquals;
@MicronautTest
public class HelloControllerTest {
@Inject
EmbeddedServer embeddedServer;
@Test
public void testIndex() throws Exception {
try(RxHttpClient client = embeddedServer.getApplicationContext().createBean(RxHttpClient.class, embeddedServer.getURL())) {
assertEquals(HttpStatus.OK, client.toBlocking().exchange("/hello").status());
}
}
}
What do we do have here ?
- @MicronautTest annotation tells Micronaut that it needs to make the ApplicationContext available for our test
- We use a constructor injection to inject an embedded server into our test
- @test is a simple JUnit 5 annotation, nothing new here
- Using the application context of the embedded server, we will create a simple HTTP Client bean and we configure it to connect to our embedded server (dynamic) url
- We then simply execute a blocking GET request to test our very first controller method
➡️ Although this test will work (we are returning a string within an HTTP 200), let's modify it to test our "hello" string response instead:
@MicronautTest
public class HelloControllerTest {
@Inject
EmbeddedServer embeddedServer;
@Test
public void testIndex() throws Exception {
try(RxHttpClient client = embeddedServer.getApplicationContext().createBean(RxHttpClient.class, embeddedServer.getURL())) {
assertEquals("Hello Micronaut !", client.retrieve("/hello").blockingFirst());
}
}
}
We are now ready to run the test using maven wrapper:
> ./mvnw test
Once finished, you should see an output similar to:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.stacklabs.micronaut.workshop.agency.HelloControllerTest
23:18:45.538 [main] INFO i.m.context.env.DefaultEnvironment - Established active environments: [test]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.62 s - in com.stacklabs.micronaut.workshop.agency.HelloControllerTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
Running the app
You probably love tests but you might want to run the app, so let's do that.
➡️ First let's compile and package our application:
> ./mvnw clean install
By default Micronaut will add Shadow Jar dependencies and plugins to our build so it generates two jars, the "original" one that only contain
our application classes, and a self-contained executable "uber-jar" (or fat-jar) that we can deploy as is.
➡️ Let's check the output of our build:
> ls -l target/
-rw-rw-r-- 1 user group 13M mars 10 13:14 hello-world-0.1.jar
-rw-rw-r-- 1 user group 11K mars 10 13:14 original-hello-world-0.1.jar
➡️ We can now launch our app using the fat-jar and interact with it using our browser or curl:
> java -jar target/hello-world-0.1.jar
13:24:15.361 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 968ms. Server Running: http://localhost:8080
> curl http://localhost:8080/hello
Hello Micronaut !
That's it, we now have a working REST application that we can test, compile and run !
Going further...
...with Micronaut
Of course we only created and developed a simple hello-world application so you might be a bit frustrated, but the goal of this article was simply showing how simple it is to both create and develop a Micronaut application.
You may ask yourself: where should I start to dive further into Micronaut framework ? Well it all depends on what you are trying to achieve but here are some hints:
- Native compilation with GraalVM, to generate native binaries that run in a few milliseconds.
- Complete REST servers with URL and body params, reactive APIs and HTTP clients to both test your servers and interact with external services such as remote HTTP APIs.
- Configuration and auto-configuration
- Data integration with Micronaut Data for relational databases or support for various data provider such as MongoDB, Redis or Cassandra
- Microservices observability with Management & Monitoring
- Cloud native features with support for various cloud through auto-configuration and such, service discovery or serverless functions.
- Standalone CLI applications with the great Picocli ♥️ parser integration !
- ... and so much more !
...beyond Micronaut
As stated in the introduction we now have quite a lot of new JVM frameworks that aim to answer today's microservices needs. Although we won't compare them to Micronaut in this article (maybe in a future article ?), I'll still leave some links here in case you want to dig into one of them:
- Quarkus by RedHat, to write "Supersonic Subatomic Java"
- Helidon by Oracle, a "collection of Java librairies for writing microservices"
- MicroProfile by Eclipse, a project "aimed at optimizing Enterprise Java for the microservices architecture"
- SpringBoot by Pivotal, with experimental (and hopefully future) GraalVM native images support
References:
I can only recommend the great Micronaut documentation that provides most of the information you need. You can basically find it in two places:
- Micronaut user guide that covers most of the features Micronaut provides
- Micronaut documentation home page that provides links to specific guides. Very useful when you need to work on specific topics such as Micronaut with GCP, Kafka or Views.