Building Modern Microservices with Quarkus

The next generation of “Supersonic Subatomic” Java applications

Jonathan Manera
4 min read4 days ago
Photo by Hermeus on Unsplash

Microservices architecture has become one of the most popular choices for building scalable, maintainable, and resilient applications. The goal of this article is to show you how you can easily build modern microservices with Quarkus.

Why Choose Quarkus?

Known as “Supersonic Subatomic Java Framework”, Quarkus promises:

  • Developer Joy: Zero config, live reload , streamlined code for 80% of common usages, flexible for the remaining 20%.
  • Microservice First: Fast boot times, rapid scalability, and low resource usage.
  • Cloud Native: Embraces 12 factor architecture in environments like Kubernetes.
  • Reactive Core: Built on a reactive core, ensures fast and efficient performance.
  • Standard-based: Based on industry standards and frameworks — including Jakarta EE, Hibernate, Vert.x, MicroProfile, and OpenTelemetry.

REST Service Example

Generating the Quarkus Project

You can easily create a Quarkus project using the Quarkus Getting Starter Site. This site allows you to generate your project with code examples and all the dependencies you need for your microservices.

In this example, we chose the REST service extension preset that provides a Gradle project with the 3.15 version of Quarkus (the LTS version at the time I’m writing this article), and the quarkus-rest dependency (A Jakarta REST implementation).

Project Structure

The Quarkus project structure is designed to be intuitive and well organized, making it easy to develop, test, and deploy applications.

You’ll find the following structure:

.
├── README.md
├── build.gradle
├── gradle.properties
├── settings.gradle
└── src
├── main
│ ├── docker
│ │ ├── Dockerfile.jvm
│ │ ├── Dockerfile.legacy-jar
│ │ ├── Dockerfile.native
│ │ └── Dockerfile.native-micro
│ ├── java
│ │ └── ...
│ └── resources
│ └── application.properties
├── native-test
│ └── java
│ └── ...
└── test
└── java
└── ...

Here:

  • src/main/java/ — Contains the application code (e.g., REST endpoints, business logic).
  • src/main/resources/ — Holds Quarkus’ configuration files and other static assets.
  • src/main/docker/— Provides Dockerfiles for building different container images. Containerization is crucial for deploying microservices.
  • src/test/java/— Contains unit and integration tests for the application.
  • src/native-test/java/— Contains tests that run against the native image.

The Jakarta REST Resource

Here’s an example of a simple REST endpoint:

import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.infrastructure.Infrastructure;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;

import java.util.Set;

@Path("/mask")
public class MaskResource {

private static final String DIGITS_PATTERN = "\\d+";
private static final Set<Integer> VALID_CARD_LENGTHS = Set.of(
13, 14, 15, 16, 19);

@Inject
MaskService maskService;

@GET
@Path("/{cardNumber}")
@Produces(MediaType.TEXT_PLAIN)
public Uni<String> getMaskedCardNumber(@PathParam("cardNumber") String cardNumber) {
boolean isValidCard = cardNumber.matches(DIGITS_PATTERN) &&
VALID_CARD_LENGTHS.contains(cardNumber.length());

if (isValidCard) {
return maskService.mask(cardNumber)
.runSubscriptionOn(Infrastructure.getDefaultExecutor());
} else {
return Uni.createFrom().emitter(
ue -> ue.fail(new BadRequestException("Invalid credit card.")));
}
}
}

The MaskResource class is annotated with @Path("/mask") and defines a GET endpoint that accepts a credit card number as a path parameter. After validating the card number, the resource delegates the masking logic to the MaskService.

Note that Quarkus uses SmallRye Mutiny for reactive programming — This reactive library simplifies asynchronous processing with non-blocking I/O.

MaskService is injected via the @Inject annotation. Our next topic will be dependency injection.

Using Injection

Dependency injection is powered by ArCa Context and Dependency Injection (CDI) solution optimized for Quarkus’ architecture.

import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class MaskService {

private static final String MASK_CHARACTER = "X";

public Uni<String> mask(String cardNumber) {
int numDigitsToMask = Math.max(0, cardNumber.length() - 4);
String maskedDigits = MASK_CHARACTER.repeat(numDigitsToMask);
String lastDigits = cardNumber.substring(cardNumber.length() - 4);

return Uni.createFrom().item(maskedDigits + lastDigits);
}
}

Here the MaskService class is a simple service that demonstrates how to process input data and return a transformed result.

The @ApplicationScoped annotation ensures that the service is managed by Quarkus’ CDI container.

Exception Handling

Jakarta provides the ExceptionMapper interface to handle exceptions thrown by the application and map them to appropriate HTTP responses.

import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;

@Provider
public class BadRequestExceptionMapper implements ExceptionMapper<BadRequestException> {

@Override
public Response toResponse(BadRequestException ex) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity(ex.getMessage())
.build();
}
}

Here the BadRequestExceptionMapper provider returns an HTTP 400 status code along with an error message when the BadRequestException is thrown.

The @Provider annotation registers the mappers with the Jakarta REST runtime.

Testing

Quarkus supports JUnit 5 and RestAssured tests.

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
class MaskResourceTest {

@Test
void testMaskEndpoint_Success() {
given()
.when().get("/mask/5555555555554444")
.then()
.statusCode(200)
.body(is("XXXXXXXXXXXX4444"));
}

@Test
void testMaskEndpoint_Fail() {
given()
.when().get("/mask/555555555555")
.then()
.statusCode(400)
.body(is("Invalid credit card."));
}
}

The @QuarkusTest annotation enables the Quarkus test runner that starts the application in test mode.

Conclusion

In this article, we’ve covered the practical steps to get started building efficient and scalable HTTP microservices using Quarkus.

Start experimenting with this modern Java framework by exploring the official Quarkus guides.

Thanks for reading. I hope this was helpful!

The example code is available on GitHub.

--

--

Jonathan Manera
Jonathan Manera

Written by Jonathan Manera

If you wish to make a Java app from scratch, you must first invent the universe.

No responses yet