Quarkus para desenvolvedores Spring: Java nativo com startup em milissegundos

1. Por que Quarkus? O contexto para desenvolvedores Spring

1.1. O problema da latência em microsserviços com Spring Boot tradicional

Desenvolvedores Spring conhecem bem o custo do ecossistema: uma aplicação Spring Boot típica consome 200-400 MB de RAM e leva de 5 a 15 segundos para iniciar. Em ambientes serverless ou com containers efêmeros (Kubernetes com escala automática), esse overhead se torna um gargalo real. Cada vez que um pod é criado, a JVM precisa carregar classes, processar annotations e inicializar o contexto — um luxo que sistemas modernos não podem pagar.

1.2. Quarkus como alternativa: foco em startup rápida e baixo consumo de memória

Quarkus foi projetado para resolver exatamente esse problema. Com compilação Ahead-of-Time (AOT) e integração nativa com GraalVM, uma aplicação Quarkus pode iniciar em 10-50 milissegundos e consumir 10-50 MB de RAM no modo nativo. Para quem vem do Spring, a curva de aprendizado é curta: a sintaxe é familiar, o ecossistema é maduro e a produtividade é comparável.

1.3. Compatibilidade com o ecossistema Java e familiaridade para quem vem do Spring

Quarkus não reinventa a roda. Ele utiliza padrões Jakarta EE (CDI, JAX-RS, Bean Validation) e oferece extensões que mapeiam diretamente os starters do Spring. Se você conhece Spring Boot, consegue escrever um microsserviço Quarkus produtivo em menos de um dia.

2. Arquitetura e conceitos fundamentais do Quarkus

2.1. Compilação Ahead-of-Time (AOT) vs Just-in-Time (JIT): o segredo da inicialização em milissegundos

No Spring Boot tradicional, a JVM interpreta bytecode e otimiza em tempo de execução (JIT). No Quarkus, a compilação AOT transforma o bytecode em binário nativo durante o build. Isso elimina o tempo de warm-up e reduz drasticamente a pegada de memória.

# Modo JVM tradicional (Spring Boot):
# 1. JVM inicia → 2. Carrega classes → 3. Interpreta bytecode → 4. JIT otimiza
# Tempo total: 5-15 segundos

# Modo nativo (Quarkus + GraalVM):
# 1. Binário nivo executa diretamente → 2. Sem interpretação, sem JIT
# Tempo total: 10-50 milissegundos

2.2. Extensões Quarkus: substituindo starters do Spring por dependências modulares

Assim como o Spring Boot tem starters (spring-boot-starter-web), o Quarkus tem extensões. A diferença é que elas são ativadas apenas no build, reduzindo o classpath em runtime.

<!-- Spring Boot: spring-boot-starter-web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Quarkus: resteasy-reactive -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>

2.3. Modo dev e live reload: produtividade comparada ao Spring Boot DevTools

Quarkus oferece quarkus:dev (Maven) ou quarkusDev (Gradle) que faz live reload automático — sem necessidade de reiniciar a JVM. É mais rápido que o Spring Boot DevTools porque usa classloading otimizado.

# Terminal 1: Iniciar modo dev
./mvnw quarkus:dev

# Terminal 2: Alterar código → reload em < 1 segundo
# Acessar http://localhost:8080/hello

3. Mapeando o Spring para o Quarkus: injeção de dependência e configuração

3.1. CDI (Contexts and Dependency Injection) vs Spring IoC: semelhanças e diferenças práticas

A injeção de dependência no Quarkus usa CDI (Jakarta Contexts and Dependency Injection). As anotações são quase idênticas:

// Spring
@Service
public class MeuServico {
    @Autowired
    private Repositorio repositorio;
}

// Quarkus (CDI)
@ApplicationScoped
public class MeuServico {
    @Inject
    Repositorio repositorio;
}

3.2. Configuração com application.properties e application.yml: reaproveitando conhecimento

Quarkus usa o mesmo formato de configuração do Spring Boot. Você pode copiar seu application.properties e ajustar apenas os prefixos:

# Spring Boot
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.jpa.hibernate.ddl-auto=update

# Quarkus
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/mydb
quarkus.hibernate-orm.database.generation=update

3.3. Profiles e ambiente: como gerenciar configurações por estágio (dev, test, prod)

O sistema de profiles do Quarkus é idêntico ao do Spring:

# application.properties (padrão)
quarkus.http.port=8080

# application-dev.properties
quarkus.http.port=8081
quarkus.log.level=DEBUG

# application-prod.properties
quarkus.http.port=80
quarkus.log.level=WARN

Ative com -Dquarkus.profile=dev ou variável de ambiente QUARKUS_PROFILE=prod.

4. Construindo uma API REST: do Spring MVC ao JAX-RS

4.1. Anotações REST: @Path e @GET (JAX-RS) vs @RequestMapping e @GetMapping (Spring)

A transição é direta. O Quarkus usa JAX-RS (Jakarta RESTful Web Services), que é o padrão Java EE:

// Spring MVC
@RestController
@RequestMapping("/api")
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, Spring!";
    }
}

// JAX-RS (Quarkus)
@Path("/api")
public class HelloResource {
    @GET
    @Path("/hello")
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello, Quarkus!";
    }
}

4.2. Tratamento de exceções e respostas HTTP: ExceptionMapper vs @ControllerAdvice

No Spring, usamos @ControllerAdvice com @ExceptionHandler. No Quarkus, implementamos ExceptionMapper<RuntimeException>:

// Spring
@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<String> handleNotFound(ResourceNotFoundException ex) {
        return ResponseEntity.status(404).body(ex.getMessage());
    }
}

// Quarkus
@Provider
public class NotFoundExceptionMapper implements ExceptionMapper<ResourceNotFoundException> {
    @Override
    public Response toResponse(ResourceNotFoundException exception) {
        return Response.status(404).entity(exception.getMessage()).build();
    }
}

4.3. Validação de entrada com Bean Validation: compatibilidade total com Jakarta Validation

A validação é idêntica — ambos usam Jakarta Bean Validation:

// Válido tanto no Spring quanto no Quarkus
public class UsuarioDTO {
    @NotNull
    @Size(min = 3, max = 50)
    private String nome;

    @Email
    private String email;
}

5. Persistência de dados: Hibernate Panache vs Spring Data JPA

5.1. Panache Repository: menos boilerplate que Spring Data JPA

Panache oferece uma abordagem mais enxuta, com métodos estáticos e padrão Active Record:

// Spring Data JPA
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
    List<Usuario> findByNome(String nome);
}

// Quarkus Panache (Active Record)
public class Usuario extends PanacheEntity {
    public String nome;
    public String email;

    public static List<Usuario> findByNome(String nome) {
        return find("nome", nome).list();
    }
}

// Uso no serviço
List<Usuario> usuarios = Usuario.findByNome("João");

5.2. Configuração de datasources e pooling: Quarkus com Agroal vs HikariCP

Quarkus usa Agroal como pool de conexões padrão, mas é totalmente compatível com HikariCP:

# Configuração de datasource no Quarkus
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/mydb
quarkus.datasource.jdbc.max-size=20
quarkus.datasource.jdbc.min-size=5

# Pool HikariCP (opcional)
quarkus.datasource.jdbc.pool-type=HikariCP

5.3. Migrações com Flyway ou Liquibase: integração nativa e scripts SQL reaproveitáveis

Ambas as ferramentas são suportadas nativamente. Basta adicionar a extensão e criar os scripts:

<!-- Adicionar extensão Flyway -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-flyway</artifactId>
</dependency>

<!-- Script SQL: src/main/resources/db/migration/V1__create_usuario.sql -->
CREATE TABLE usuario (
    id BIGSERIAL PRIMARY KEY,
    nome VARCHAR(100) NOT NULL,
    email VARCHAR(150) UNIQUE NOT NULL
);

6. Geração de imagem nativa com GraalVM

6.1. Pré-requisitos e instalação do GraalVM

Instale o GraalVM 22+ e configure a variável GRAALVM_HOME:

# Instalar via SDKMAN (recomendado)
sdk install java 22.3.0.r17-grl

# Verificar instalação
java -version
# Saída esperada: OpenJDK 64-Bit Server VM (build 22.3.0, mixed mode, sharing)

6.2. Compilando o primeiro binário nativo: comandos Maven/Gradle e resultados de desempenho

# Compilar imagem nativa (Maven)
./mvnw package -Pnative

# Executar binário nativo
./target/quarkus-app-runner

# Resultado esperado:
# -- Quarkus started in 0.015s (startup em 15 milissegundos!)
# -- Listening on: http://0.0.0.0:8080

6.3. Limitações e cuidados: reflexão, recursos dinâmicos e bibliotecas incompatíveis

Algumas bibliotecas usam reflexão em tempo de execução, o que não funciona em modo nativo. Soluções:

# Registrar classes para reflexão no application.properties
quarkus.native.additional-build-args=\
  --initialize-at-build-time=com.minha.biblioteca,\
  -H:ReflectionConfigurationFiles=reflection-config.json

# Evitar: Class.forName(), Proxy dinâmico, serialização Java padrão

7. Observabilidade e produção: métricas, logs e health checks

7.1. Micrometer no Quarkus: métricas compatíveis com Prometheus, similar ao Spring Actuator

<!-- Adicionar extensão Micrometer -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-micrometer-registry-prometheus</artifactId>
</dependency>

# Configuração
quarkus.micrometer.export.prometheus.enabled=true
quarkus.micrometer.binder.http-server.enabled=true

# Endpoint: http://localhost:8080/q/metrics

7.2. Logging estruturado com Logback ou JBoss Logging: adaptando configurações existentes

Quarkus usa JBoss Logging por padrão, mas aceita Logback:

# Configuração básica de logging
quarkus.log.level=INFO
quarkus.log.console.enable=true
quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}] (%t) %s%e%n

# Para JSON estruturado (compatível com ELK)
quarkus.log.console.json=true

7.3. Health checks e readiness/liveness probes: endpoints prontos para Kubernetes

<!-- Adicionar extensão de health -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-health</artifactId>
</dependency>

# Endpoints automáticos:
# /q/health/live  → Liveness probe
# /q/health/ready → Readiness probe
# /q/health       → Health check completo

8. Quando migrar (e quando não migrar) do Spring para o Quarkus

8.1. Cenários ideais: serverless, containers efêmeros e ambientes com restrição de memória

  • Serverless (AWS Lambda, Azure Functions): startup em milissegundos é essencial
  • Kubernetes com escala automática: pods que iniciam e morrem rapidamente
  • Edge computing: dispositivos com pouca RAM (Raspberry Pi, gateways)
  • Microsserviços de alta rotatividade: dezenas de instâncias com tráfego variável

8.2. Cenários desfavoráveis: aplicações monolíticas complexas com muitas bibliotecas proprietárias

  • Aplicações monolíticas com 50+ módulos: o build nativo pode falhar por reflexão
  • Bibliotecas proprietárias que usam serialização Java: incompatível com GraalVM
  • Equipes sem experiência em GraalVM: a curva de debugging em modo nativo é mais íngreme
  • Projetos com dependências de runtime dinâmicas: Class.forName() ou service loaders

8.3. Estratégia de migração gradual: coexistência de módulos Spring e Quarkus no mesmo projeto

É possível manter ambos os frameworks no mesmo repositório, usando módulos Maven:

meu-projeto/
├── modulo-spring/     # Microsserviços legados em Spring Boot
│   └── pom.xml
├── modulo-quarkus/    # Novos microsserviços em Quarkus
│   └── pom.xml
└── modulo-comum/      # Classes compartilhadas (DTOs, entidades)
    └── pom.xml

A comunicação entre módulos pode ser feita via REST, mensageria (Kafka/RabbitMQ) ou banco de dados compartilhado. A migração é incremental: comece pelos endpoints de baixa complexidade e alto benefício (serverless, APIs de consulta).


Referências