Core Java

From Legacy to Modern: Refactoring Java EE Monoliths into Quarkus Microservices

A Practical Guide to Breaking Monoliths with Quarkus (and Staying Sane)

Let’s face it: Many enterprises still run on Java EE monoliths.

These applications are often massive, battle-tested, and… hard to touch.

But business moves faster now. You’re asked for:

  • Faster release cycles
  • Cloud-native readiness
  • Lower infrastructure costs
  • Containerization

And here’s where things get tricky:

How do you safely refactor a Java EE monolith into modern Quarkus microservices without breaking everything?

This guide walks you through step-by-step strategies for doing just that.

We’ll cover:

  • How to extract services carefully
  • Mapping CDI to Spring or Quarkus DI
  • Dockerizing legacy code safely
  • Lessons from real migrations

No silver bullets—just realistic advice for developers working in the trenches.

Why Quarkus?

Quarkus is a modern, Kubernetes-friendly Java stack designed for:

  • Fast boot times
  • Low memory usage (GraalVM native images optional)
  • Developer productivity (hot reload, dev services)

It’s a great fit for moving legacy Java EE workloads to containers and the cloud.

Quarkus supports both:

  • Jakarta EE standards (JAX-RS, CDI, JPA)
  • MicroProfile and reactive programming with Vert.x
  • Spring compatibility mode (more on that later)

Step 1: Understand Your Monolith

Before splitting anything, map out your monolith’s structure:

What to Look ForWhy It Matters
Business Domains (e.g., Billing, Customer, Orders)Candidate services
Shared libraries or utilitiesIdentify tight coupling
Database schema ownershipDefine data boundaries
Session state usageStatelessness is key for microservices
Legacy APIs (JAX-RS, SOAP)Determine what can be reused

Step 2: Identify the First Service to Extract

Don’t try to “microservice all the things” at once. Pick one service to extract first.

Criteria for a Good Candidate:

  • Business boundary is clear (e.g., Inventory, User Management)
  • Minimal dependencies on other modules
  • Low-risk for failures during the split

Step 3: Migrate Code to Quarkus

Quarkus supports both CDI and a Spring-like API layer.
If you’re coming from Java EE, you’ll mostly use CDI annotations.

Mapping Java EE to Quarkus

Java EEQuarkus Equivalent
@Stateless, @Singleton@ApplicationScoped, @Singleton (Quarkus CDI)
@Inject@Inject (Quarkus CDI works the same)
@EJBUse @Inject with plain beans (EJB is not needed anymore)
@Path, @GET, @POSTSame JAX-RS annotations (Quarkus supports JAX-RS natively)
@PersistenceContextUse @Inject EntityManager
JTA transactions@Transactional (Quarkus has ArC support)

Example Migration

Java EE Style:

@Stateless
@Path("/users")
public class UserService {
    @PersistenceContext
    EntityManager em;

    @GET
    public List<User> getUsers() {
        return em.createQuery("SELECT u FROM User u").getResultList();
    }
}

Quarkus Version:

@Path("/users")
@ApplicationScoped
@Transactional
public class UserService {

    @Inject
    EntityManager em;

    @GET
    public List<User> getUsers() {
        return em.createQuery("SELECT u FROM User u").getResultList();
    }
}

Step 4: Handle Configuration and Environment

In Java EE, you might have used web.xml or application servers for configuration.

In Quarkus, switch to application.properties or application.yaml:

quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=dbuser
quarkus.datasource.password=secret

Quarkus also supports:

  • MicroProfile Config
  • Kubernetes/OpenShift environment variables

Step 5: Dockerize the Service

Quarkus makes Dockerization easy.

Use quarkus.container-image.docker extension or create a simple Dockerfile:

FROM quay.io/quarkus/ubi-quarkus-native-image:latest
COPY target/*-runner /application
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

Or for JVM mode:

FROM eclipse-temurin:21-jdk
COPY target/quarkus-app/ /app/
CMD ["java", "-jar", "/app/quarkus-run.jar"]

Step 6: Deploy and Test

Run the service locally with:

./mvnw compile quarkus:dev

Deploy to Kubernetes or OpenShift using:

./mvnw clean package -Dquarkus.kubernetes.deploy=true

Lessons Learned from Real Migrations

1️⃣ Don’t Over-Split

Splitting a monolith into too many microservices too fast creates:

  • Network overhead
  • Complex deployments
  • Service sprawl

Start small. Grow as needed.

2️⃣ Use APIs to Extract, Not Rewrite Everything

Expose parts of the monolith as APIs (REST or gRPC) before breaking them apart physically.

This allows gradual separation.

3️⃣ Watch for Database Coupling

If your services share the same database schema, you haven’t truly decoupled.

Options:

  • Use database views or proxies temporarily
  • Move toward dedicated schemas per service over time

4️⃣ Beware of Stateful Logic

Java EE apps often rely on:

  • HTTP sessions
  • EJB session beans

Microservices should be stateless. Move state to:

  • Redis or distributed caches
  • Databases

5️⃣ Leverage Quarkus Dev Mode

Quarkus provides hot reload and dev services. Use them for fast feedback.

./mvnw quarkus:dev

Useful Tools and Links

Final Thoughts

Migrating from a Java EE monolith to Quarkus microservices is doable—but it’s not trivial.

  • Start small
  • Extract APIs gradually
  • Dockerize as you go
  • Take lessons from real-world cases, not just tutorials

Modernization is a marathon, not a sprint.
Quarkus can help you run it faster—but only if you pace yourself.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button