Quarkus vs Spring Boot

What is Quarkus ?

  • Quarkus is a Kubernetes-native Java framework designed for building high-performance, cloud-native, and serverless applications. It is optimized for fast startup times, low memory usage, and seamless integration with containers.

 

Key Features of Quarkus:

Supersonic & Subatomic: Super-fast startup times (milliseconds) and a tiny memory footprint
Cloud & Kubernetes Native: Optimized for running in containers and serverless environments
Developer-Friendly: Live coding, fast reloads, and easy configuration
Works with JVM & Native: Runs efficiently on traditional JVM and compiles natively with GraalVM
Built for Modern Architectures: Supports reactive programming, microservices, and event-driven applications
Rich Ecosystem: Compatible with Hibernate, RESTEasy, Kafka, OpenTelemetry, and more

Why Use Quarkus?

  • Ideal for Microservices: Perfect for cloud-based, microservices-driven architectures
  • Ultra-Fast Performance: Starts up in milliseconds, ideal for scaling dynamically
  • Reduced Infrastructure Costs: Uses less memory and CPU, saving cloud costs
  • Improves Developer Productivity: Hot reload, Dev UI, and powerful tooling

Setting up application

  1. Go to https://code.quarkus.io/
  2. Generate the project
  3. Unzip the jar
  4. Open the project in Intellij Idea

build.gradle

plugins {
    id 'java'
    id 'io.quarkus'
}

repositories {
    mavenCentral()
    mavenLocal()
}

dependencies {
    implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
    implementation 'io.quarkus:quarkus-resteasy'
    implementation 'io.quarkus:quarkus-resteasy-jackson'
    implementation 'io.quarkus:quarkus-arc'
    testImplementation 'io.quarkus:quarkus-junit5'
    testImplementation 'io.rest-assured:rest-assured'
}

group 'org.acme'
version '1.0.0-SNAPSHOT'

java {
    sourceCompatibility = JavaVersion.VERSION_21
    targetCompatibility = JavaVersion.VERSION_21
}

test {
    systemProperty "java.util.logging.manager", "org.jboss.logmanager.LogManager"
}
compileJava {
    options.encoding = 'UTF-8'
    options.compilerArgs << '-parameters'
}

compileTestJava {
    options.encoding = 'UTF-8'
}

Try Live Reloading

Build and Run Jar

  • The application can be packaged using:

 

  • In case if test task is failing change the set the following property in application.properties

 

  • It produces the quarkus-run.jar file in the build/quarkus-app/ directory. 

 

  • The application is now runnable using
./gradlew build
java -jar build/quarkus-app/quarkus-run.jar
quarkus.http.test-port=9001

Create and Run docker image of a quarkus app

 docker build -f src/main/docker/Dockerfile.jvm -t my-q-app .

Build the image

Run the container from the image

docker container run -p 8080:8080  --name quarkus-app -d my-q-app

Compare quarkus app with spring boot app using jconsole

Simple Crud Operations

package org.acme;


import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Path("/mobiles")
public class MobileResource {

    List<String> mobiles = new ArrayList<>(Arrays.asList("iPhone", "Samsung", "OnePlus"));

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public Response getMobile() {
        return Response.ok(mobiles).build();
    }

    @POST
    @Consumes(MediaType.TEXT_PLAIN)
    public Response saveMobile(String mobileName) {
        mobiles.add(mobileName);
        return Response.ok(mobiles).build();
    }

    @PUT
    @Consumes(MediaType.TEXT_PLAIN)
    public Response updateMobile(@QueryParam("oldName") String oldName, @QueryParam("newName") String newName) {
        int index = mobiles.indexOf(oldName);
        mobiles.set(index, newName);
        return Response.ok(mobiles).build();
    }

    @DELETE
    @Path("/{name}")
    @Consumes(MediaType.TEXT_PLAIN)
    public Response deleteMobile(@PathParam("name") String name) {
        mobiles.remove(name);
        return Response.ok(mobiles).build();
    }
}

List Extensions

Add Quarkus Extension

Microprofile Rest Client

plugins {
    id 'java'
    id 'io.quarkus'
}

repositories {
    mavenCentral()
    mavenLocal()
}

dependencies {
    implementation 'io.quarkus:quarkus-smallrye-fault-tolerance'
    implementation 'io.quarkus:quarkus-smallrye-openapi'
    implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
    implementation 'io.quarkus:quarkus-resteasy'
    implementation 'io.quarkus:quarkus-resteasy-jackson'
    implementation 'io.quarkus:quarkus-arc'
    implementation 'io.quarkus:quarkus-resteasy-client-jackson'
    testImplementation 'io.quarkus:quarkus-junit5'
    testImplementation 'io.rest-assured:rest-assured'
}

group 'org.acme'
version '1.0.0-SNAPSHOT'

java {
    sourceCompatibility = JavaVersion.VERSION_21
    targetCompatibility = JavaVersion.VERSION_21
}

test {
    systemProperty "java.util.logging.manager", "org.jboss.logmanager.LogManager"
}
compileJava {
    options.encoding = 'UTF-8'
    options.compilerArgs << '-parameters'
}

compileTestJava {
    options.encoding = 'UTF-8'
}
package org.acme;

import java.net.URL;

public class TvSeries  {
    private int id;
    private URL url;
    private String name;
    private String type;
    private String  language;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public URL getUrl() {
        return url;
    }

    public void setUrl(URL url) {
        this.url = url;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }
}
package org.acme;

import jakarta.json.JsonArray;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.QueryParam;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import java.util.List;

@RegisterRestClient(baseUri = "https://api.tvmaze.com")
public interface TvSeriesProxy {

    //https://api.tvmaze.com/shows/169

    @GET
    @Path("/shows/{id}")
    TvSeries getTvSeriesById(@PathParam("id") int id);

    @GET
    @Path("/search/people")
    List<Object> searchPeople(@QueryParam("q") String query);
}
package org.acme;

import jakarta.json.JsonArray;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.eclipse.microprofile.rest.client.inject.RestClient;

import java.util.List;

@Path("/tvseries")
public class TvSeriesResource {

    @RestClient
    TvSeriesProxy tvSeriesProxy;

    @GET
    @Path("/{id}")
    public TvSeries getTvSeriesById(@PathParam("id") int id) {
        return tvSeriesProxy.getTvSeriesById(id);
    }

    @GET
    @Path("/search/{name}")
    public List<Object> getTvSeriesByName(@PathParam("name") String name) {
        List<Object> tvSeries = tvSeriesProxy.searchPeople(name);
        System.out.println(tvSeries);
        return tvSeries;
    }
}

Fault tolerance

Extension needed for fault tolerance : quarkus-smallrye-fault-tolerance  

@GET
    @Fallback(
        fallbackMethod = "fallback",
        applyOn = {RuntimeException.class} ,
        skipOn = {IllegalArgumentException.class}
    )
    @Path("/{id}")
    public Response getTvSeriesById(@PathParam("id") int id) {
        return Response.ok(tvSeriesProxy.getTvSeriesById(id)).build();
    }

    public Response fallback(int id) {
        return Response.ok("Site is under maintainance").build() ;
    }
@GET
    @Fallback(
        fallbackMethod = "fallback",
        applyOn = {RuntimeException.class} ,
        skipOn = {IllegalArgumentException.class}
    )
    @Retry(maxRetries =  3, delay = 3000)
    @Path("/{id}")
    public Response getTvSeriesById(@PathParam("id") int id) {
        System.out.println("Trying to access TV Series with id ::::::::: "+id);
        return Response.ok(tvSeriesProxy.getTvSeriesById(id)).build();
    }

    public Response fallback(int id) {
        return Response.ok("Site is under maintainance").build() ;
    }

Retry Mechanism

Circuit Breaker

@GET
    @Fallback(
        fallbackMethod = "fallback",
        applyOn = {RuntimeException.class} ,
        skipOn = {IllegalArgumentException.class}
    )
    @CircuitBreaker( requestVolumeThreshold = 4, failureRatio = 0.75, delay = 5000)
    @Path("/{id}")
    public Response getTvSeriesById(@PathParam("id") int id) {
        System.out.println("Trying to access TV Series with id ::::::::: "+id);
        return Response.ok(tvSeriesProxy.getTvSeriesById(id)).build();
    }

    public Response fallback(int id) {
        return Response.ok("Site is under maintainance").build() ;
    }
while true; do sleep 1;curl http://localhost:8080/tvseries/169; echo -e '\n';done

Dependency Injection

package org.acme;


import jakarta.annotation.PostConstruct;
import jakarta.inject.Singleton;

@Singleton
public class MyGreeting {

        @PostConstruct
        void init() {
            System.out.println("MyGreeting bean created");
        }

        public String greet() {
            return "This is greeting from a bean";
        }

}
package org.acme;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/hello")
public class GreetingResource {

    @Inject
    MyGreeting myGreeting;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return myGreeting.greet();
    }
}

Hibernate with QUARKUS

application.properties

quarkus.datasource.db-kind=mysql
quarkus.datasource.jdbc.url=jdbc:mysql://localhost:3306/mydb
quarkus.datasource.username=root
quarkus.datasource.password=
quarkus.hibernate-orm.database.generation=update
quarkus.hibernate-orm.log.sql=true
package org.quarkus.hibernate.demo;

import jakarta.persistence.*;

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long id;

    public String name;
    public String email;
}
package org.quarkus.hibernate.demo;

import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class UserRepository implements PanacheRepository<User> {
    public User findByEmail(String email) {
        return find("email", email).firstResult();
    }
}
package org.quarkus.hibernate.demo;

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import java.util.List;

@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {

    @Inject
    UserRepository userRepository;

    @GET
    public List<User> getAllUsers() {
        return userRepository.listAll();
    }

    @POST
    @Transactional
    public void addUser(User user) {
        userRepository.persist(user);
    }
}
curl -X POST http://localhost:8080/users -H "Content-Type: application/json" -d '{"name": "Alice", "email": "alice@example.com"}'
curl -X GET http://localhost:8080/users

quarkus

By Pulkit Pushkarna

quarkus

  • 20