Micronaut and Quarkus are two cool emerging Java backend frameworks that aim to solve some problems that exist in current frameworks, like faster startup, low memory footprint, and support for ahead-of-time compilation using GraalVM. In this session, we'll square off both frameworks against each other.
How do they compare, what are the stronger and weaker points of both frameworks?
We'll compare the following features:
Initializing your project
Building your first restcontroller / programming model
Startup time
Database support
Integration test support
Building native images
Memory usage and JAR sizes
Ease of cloud deployment
In the end, we might have a clear winner! ... or will we?
3. What issues do Micronaut and Quarkus address?
• Lower memory footprint, faster startup
• Ahead-Of-Time (AOT) compilation
• Capability to build native images with GraalVM
• Designed from the ground up with
microservices in mind
• Built-in support for Fault Tolerance
• Monitoring / metrics
• Service discovery
• Cloud deployment
4. Project source
Website
Start/ Backed by
Github stars
#Contributors
Build tools
Languages
First Commit
github: micronaut-
project/micronaut-
core
micronaut.io
ObjectComputing
2.9K
163
Maven, Gradle
Java, Kotlin, Groovy
2017-03-16
github:
quarkusio/quarkus
quarkus.io
Red Hat
2.9K
175
Maven, Gradle
Java, Kotlin, Scala
2018-06-22
5. Round 1: Getting started
Round 2: Programming model
Round 3: Database persistency
Round 4: Test support
Round 5: Native images, startup and heap
The match
6. Conference application
Conference API
Conference Service
Conference RepositoryCountryClient
H2External country service
http(s)
GET /conferences
POST /conferences
{ “name”: “Devoxx”}
{ “name”: “Devoxx”}
{ “name”: “Devoxx”,
“countryName”: “Belgium”}
{ “countryName”: “Belgium”}/conf/{name}/country
GET /conferences-with-country
7.
8.
9.
10.
11. Let’s start the applications!
mvn compile quarkus:dev
mvn package exec:exec
(or start the main class)
8100
8101
12.
13.
14. REST Controller
@Path("/conferences")
public class ConferenceResource {
@Inject
ConferenceService conferenceService;
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Conference> getAll() {
return conferenceService.getAll();
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
public void create(Conference conference) {
conferenceService.create(conference);
}
@Controller("/conferences")
public class ConferenceController {
@Inject
ConferenceService conferenceService;
@Get
@Produces(MediaType.APPLICATION_JSON)
public List<Conference> getAll() {
return conferenceService.getAll();
}
@Post
@Consumes(MediaType.APPLICATION_JSON)
public void create(Conference conference) {
conferenceService.create(conference);
}
Conference API
Conference Service
RepoCountryClient
15. Conference service
@Singleton
public class ConferenceService {
@Inject
private ConferenceRepository conferenceRepository;
@Inject
CountryClient countryClient;
public List<Conference> getAll() {
return conferenceRepository.findAll();
}
public void create(Conference conference) {
conferenceRepository.save(conference);
}
@Singleton
public class ConferenceService {
@Inject
private ConferenceRepository conferenceRepository;
@Inject
@RestClient
CountryClient countryClient;
public List<Conference> getAll() {
return conferenceRepository.findAll();
}
public void create(Conference conference) {
conferenceRepository.save(conference);
}
Conference API
Conference Service
RepoCountryClient
16. @Path("/")
@RegisterRestClient
@Retry(maxRetries = 3, delay = 2)
@CircuitBreaker(successThreshold = 1)
public interface CountryClient {
@GET
@Path("/conferences/{name}/country")
Country getCountry(@PathParam("name") String name);
@Client("${country.service.url}")
@Retryable(attempts = “3", delay = "1s")
@CircuitBreaker(reset = "20s")
public interface CountryClient {
@Get("/conferences/{name}/country")
Country getCountry(String name);
mypackage.CountryClient/mp-rest/url=http://localhost:9000/
mypackage.CountryClient/mp-rest/scope=javax.inject.Singleton
country.service.url=http://localhost:9000/
application.properties application.properties / yml
Conference API
Conference Service
RepoCountryClient
REST client
17. Configuration
app.helloMessage=Hi there! app.helloMessage=Hi there!
application.properties application.properties / application.yml
@ConfigProperty(name = "app.helloMessage",
defaultValue="hello default!")
String helloMessage;
@ConfigProperties(prefix = "app")
public class ConferenceConfiguration {
@Size(min= 5)
public String helloMessage;
}
@Value("${app.helloMessage:hello default!}")
String helloMessage;
@ConfigurationProperties("app")
public class ConferenceConfiguration {
@Size(min = 5)
public String helloMessage;
}
@Property("app.helloMessage")
String helloMessage;
18. Configuration profiles
app.hello-message=Hi there!
%dev.app.hello-message=Hi from dev!
%test.app.hello-message=Hi from test!
%custom.app.hello-message=Hi from custom!
app.hello-message=Hi there!
application.properties application.properties / application.yml
• dev – during quarkus:dev unless
overridden
• test- during tests
• prod – default profile
• custom
mvnw quarkus:dev –Dquarkus.profile=test
application-test.properties / application-test.yml
app.hello-message=Hi from test!
mvnw exec:exec -Dmicronaut.environments=test
19. Custom Configuration
Create service file
/META-
INF/services/org.eclipse.microprofile.config.spi.ConfigSour
ce
public class CustomConfigSource implements ConfigSource {
@Override
public Map<String, String> getProperties() {
//my own implementation
return null;
}
@Override
public String getValue(String s) {
//my own implementation
return null;
}
@Override
public String getName() {
return "CustomConfigSource";
}
}
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-discovery-client</artifactId>
</dependency>
spring:
cloud:
config:
enabled: true
uri: http://localhost:8888/
micronaut:
config-client:
enabled: true
• HashiCorp Consul & Vault Support
• AWS Parameter Store Support
• Spring Cloud config server Support
20.
21.
22. JP
@Singleton
public class ConferenceRepository {
@Inject
EntityManager entityManager;
@Transactional
public List<Conference> findAll() {
TypedQuery<Conference> query = entityManager
.createQuery("select c from Conference c",
Conference.class);
return query.getResultList();
}
@Transactional
public void save(final Conference conference) {
entityManager.persist(conference);
}
}
@Singleton
public class ConferenceRepository {
@Inject
EntityManager entityManager;
@Transactional
public List<Conference> findAll() {
TypedQuery<Conference> query = entityManager
.createQuery("select c from Conference c",
Conference.class);
return query.getResultList();
}
@Transactional
public void save(final Conference conference) {
entityManager.persist(conference);
}
}
JPA persistency Conference API
Conference Service
RepoCountryClient
23. Improved Quarkus persistency with Panache
@Entity
public class Conference extends PanacheEntity {
private String name;
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
@Singleton
public class ConferencePanacheRepository {
public List<Conference> findAll() {
return Conference.listAll();
}
@Transactional
public void save(final Conference conference) {
conference.persist();
}
}
24. Micronaut Data
@Repository
public interface ConferenceCrudRepository extends CrudRepository<Conference, Long> {
List<Conference> findByName(String name);
List<Conference> listOrderByName();
List<Conference> listOrderByNameDesc();
List<Conference> findTop3ByNameLike(String name);
}
@Repository
public interface ConferenceCrudRepository extends CrudRepository<Conference, Long> {
}
@JdbcRepository
public interface ConferenceCrudRepository extends CrudRepository<Conference, Long> {
List<Conference> findByName(String name);
List<Conference> listOrderByName();
List<Conference> listOrderByNameDesc();
List<Conference> findTop3ByNameLike(String name);
}
No more
JPA / Hibernate needed!
25.
26.
27. Integration testing
@MicronautTest(environments = { "test" })
public class ConferenceITTest {
@Inject
EmbeddedServer embeddedServer;
@Test
@Transactional
public void testConferences() {
Conference conference = new Conference();
conference.setName("Devoxx");
given().body(conference)
.port(embeddedServer.getPort())
.contentType("application/json")
.when()
.post("/conferences")
.then()
.statusCode(200);
given().port(embeddedServer.getPort())
.when()
.get("/conferences")
.then()
.extract()
.path("[0].name")
.equals(“Devoxx");
}
application-test.yml
micronaut:
server:
port: -1
@QuarkusTest
public class ConferenceIT {
@Test
@Transactional
public void testConferences() {
Conference conference = new Conference();
conference.setName("Devoxx");
given().body(conference)
.contentType("application/json")
.when()
.post("/conferences")
.then()
.statusCode(204);
given()
.when()
.get("/conferences")
.then()
.extract()
.path("[0].name")
.equals("Devoxx");
}
quarkus.http.test-port=0
application.properties
28. Integration testing with Quarkus
@QuarkusTest
public class ConferenceIT {
@Inject
ConferenceResource conferenceResource;
@Test
public void testConferenceInternal() {
conferenceResource.getAll();
}
}
Testing internally
@Mock
@ApplicationScoped
@RestClient
public class MockCountryClient implements CountryClient {
@Override
public Country getCountryOfConference(String name) {
Country country = new Country();
country.setName("Belgium");
return country;
}
}
Mocking
@MicronautTest(environments = { "test" })
public class ConferenceITTest {
@Inject
private ConferenceController conferenceController;
@Test
public void testConferencesInternal() {
conferenceController.getAll();
}
@MockBean(CountryClient.class)
CountryClient countryClient() {
final CountryClient mock = Mockito.mock(CountryClient.class);
Country country = new Country();
country.setName("Belgium");
when(mock.getCountryOfConference(isA(String.class)))
.thenReturn(country);
return mock;
}
29. Integration testing native images
@SubstrateTest
public class NativeConferenceResourceIT extends ConferenceIT {
// Execute the same tests but in native mode.
}
33. Let’s run the applications!
JVM: port 8100
Native: port 8200
JVM: port 8101
Native: port 8201
docker run -i --rm -p 8200:8080
conference-service-quarkus
docker run -i --rm -p 8201:8080
conference-service-quarkus-micronaut
40. Summary
• Small images, faster startup,
lowest mem usage
• Microprofile Programming
model
• Really fast release cycle
• Close to Spring’s programming model
• Feels little more mature
• Micronaut Data
• More extensive support for existing
cloud environments
• Could do with a nicer
persistency solution
• Few of out-of-the-box
monitoring
• I’d like an initializer site!
Quarkus uses @ConfigProperty from the microprofile specification.
Properties are read from one application.properties file. Profiles are supported. There are three pofiles: dev, test and prod out-of-the-box, but you can specifiy more.
Properties of all profiles have to be specified in either the application.properties (with % as prefix), or through jvm parameters (-D)
Micronaut uses the @Value construction known from SpringBoot. Configuration profiles are supported and implemented by different property files like –T.yml.
This seems a little more clear than with Quarkus.
Moreso, micronaut supports integration with Spring Cloud Config server.
Both quarkus and micronaut support Hibernate for database access, with automatic schema generation.
Both frameworks support entity managers, no problem. But this feels a little... Well... Midlevel.
Quarkus support Panache, which makes life a little bit easier. (show)
Micronaut has Micronuat data (formerly predator), which is now on milestone 3. It has a programming model similair to Spring Data, where you can useto make declarative interfaces, as we will see now. Evenmore, you can use this so generate repos base don pure jdbc access, which is really cool.
Definitely points to Micronaut here!
Of course jdbc cannot do stuff like lazy loading, dirty checking, optimistic locking and stuff.
Quarkus has the @QuarkusTest annotation which spins up a test version of the application. By default, it spins up on port 8081, so not a random port as we saw in Spring Boot, for example.
Mock objects are supported for stubbing test clients.
Micronaut has @MicronautTest annotation, same thing. Default is port 8081. Now, micronaut does not recieve the port binding automatically, so you have to set it yourself.
Mocking is done through the @MockBean annotation, which you can use to specify any mock you want, for example with Mockito.
You can start micronaut with a java –jar target\...jar command.
Quarkus doesn’t build fat jars of the box, so if you want to do that, you need to specify the <uberJar> configuration in your maven-quarkus-plugin.
If we start the application with java-jar, quarkus is notably quicker here then micronaut, but both are faster than SpringBoot. File size wise, there is not much difference. Quarkus fat jar is 42MB while Micronaut’s is 46MB, both definately smaller than any Spring Boot application that boast the same functionality.
Mmemory usage:
Quarkus: 425MB/176MB
SpringBoot: 547MB/428MB
Micronaut: 541MB/251MB
Points go to Quarkus here.
You can start micronaut with a java –jar target\...jar command.
Quarkus doesn’t build fat jars of the box, so if you want to do that, you need to specify the <uberJar> configuration in your maven-quarkus-plugin.
If we start the application with java-jar, quarkus is notably quicker here then micronaut, but both are faster than SpringBoot. File size wise, there is not much difference. Quarkus fat jar is 42MB while Micronaut’s is 46MB, both definately smaller than any Spring Boot application that boast the same functionality.
Mmemory usage:
Quarkus: 425MB/176MB
SpringBoot: 547MB/428MB
Micronaut: 541MB/251MB
Points go to Quarkus here.
You can start micronaut with a java –jar target\...jar command.
Quarkus doesn’t build fat jars of the box, so if you want to do that, you need to specify the <uberJar> configuration in your maven-quarkus-plugin.
If we start the application with java-jar, quarkus is notably quicker here then micronaut, but both are faster than SpringBoot. File size wise, there is not much difference. Quarkus fat jar is 42MB while Micronaut’s is 46MB, both definately smaller than any Spring Boot application that boast the same functionality.
Mmemory usage:
Quarkus: 425MB/176MB
SpringBoot: 547MB/428MB
Micronaut: 541MB/251MB
Points go to Quarkus here.
You can start micronaut with a java –jar target\...jar command.
Quarkus doesn’t build fat jars of the box, so if you want to do that, you need to specify the <uberJar> configuration in your maven-quarkus-plugin.
If we start the application with java-jar, quarkus is notably quicker here then micronaut, but both are faster than SpringBoot. File size wise, there is not much difference. Quarkus fat jar is 42MB while Micronaut’s is 46MB, both definately smaller than any Spring Boot application that boast the same functionality.
Mmemory usage:
Quarkus: 425MB/176MB
SpringBoot: 547MB/428MB
Micronaut: 541MB/251MB
Points go to Quarkus here.