Scale and Monitor Quarkus in Production

Khimananda Oli 8 min read Programming and Languages
Scale and Monitor Quarkus in Production

By Khimananda Oli | Last reviewed: August 2026

To successfully scale and monitor Quarkus in production, you must treat it as a cloud-native platform rather than a traditional Java application server. While Quarkus offers supersonic startup times and low memory footprints, these advantages vanish without proper Kubernetes resource tuning and observability instrumentation. This guide covers the exact configuration patterns I use to run resilient Quarkus microservices, integrating Horizontal Pod Autoscaling with native OpenTelemetry support to ensure your services handle load predictably.

Quarkus App/q/metrics + OTelPrometheusTempo / JaegerK8s HPAScale DecisionScrape MetricsExport TracesCPU/Memory Data
Observability and scaling architecture for Quarkus in production environments

How do you configure Prometheus metrics to scale and monitor Quarkus in production?

Quarkus does not expose metrics by default. You must explicitly add the Micrometer registry extension to generate the Prometheus-compatible data that Kubernetes and Grafana need. In my experience auditing Java microservices, missing or misconfigured metrics are the primary reason teams fail to scale and monitor Quarkus in production effectively. The framework uses Micrometer as its abstraction layer, which means you get standardized JVM and HTTP metrics out of the box once the extension is active.

Add the required dependencies

Start by adding the Prometheus registry extension. This automatically enables the /q/metrics endpoint and registers default binders for JVM memory, garbage collection, and HTTP request timers.

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-micrometer-registry-prometheus</artifactId>
</dependency>

Tune metric exposure for security and performance

In production, never expose metrics on the same port as your public API unless you have an ingress filter. Quarkus supports management interface separation, which is critical for compliance frameworks like SOC 2 where internal telemetry endpoints must be isolated from external traffic. Configure this in your application.properties:

# Enable management interface on separate port
quarkus.management.enabled=true
quarkus.management.port=9000

# Expose metrics only on management interface
quarkus.micrometer.export.prometheus.path=/metrics

# Add custom tags for better filtering in Grafana
quarkus.micrometer.binder.http-server.ignore-patterns=/q/.*
quarkus.micrometer.binder.jvm=true

This configuration ensures that your health checks and metrics are served on port 9000 while your business logic remains on port 8080. When configuring your Prometheus scrape targets, point them specifically at port 9000. This separation prevents accidental exposure of internal runtime statistics to end users and simplifies network policy enforcement in Kubernetes.

How does OpenTelemetry integration improve Quarkus observability?

Metrics tell you what is happening, but traces tell you where it is happening. For distributed systems, this distinction is everything. Quarkus has first-class OpenTelemetry support that requires minimal boilerplate. Unlike older Java frameworks that required heavy agent attachment or complex SDK initialization, Quarkus integrates tracing directly into the build process via extensions. This results in lower overhead and more accurate context propagation across reactive boundaries.

API GatewayOrder ServiceInventory SvcPostgreSQLSpan A → BSpan B → CSpan C → DSingle Trace ID propagated across all Quarkus services via W3C Context
Distributed trace flow demonstrating context propagation when you scale and monitor Quarkus in production

Enable tracing with zero code changes

Add the OpenTelemetry extension and configure the exporter. Quarkus defaults to OTLP, which is the industry standard for 2026. Avoid legacy Zipkin or Jaeger proprietary protocols unless you have a specific legacy backend requirement.

# application.properties
quarkus.otel.enabled=true
quarkus.otel.exporter.otlp.endpoint=http://otel-collector:4317
quarkus.otel.service.name=order-service
quarkus.otel.traces.sampler=parentbased_tracealways

# Sample 10% of requests in high-volume prod to control cost
# quarkus.otel.traces.sampler.arg=0.1

A common mistake I see in OpenTelemetry implementations is sampling everything in development and then forgetting to adjust rates for production. Always define a sampling strategy explicitly. For high-throughput Quarkus services processing thousands of RPS, parent-based sampling preserves complete traces for requests that matter while dropping noise. This keeps your Tempo or Jaeger storage costs manageable without losing debugging fidelity.

What Kubernetes resource settings are required for Quarkus autoscaling?

Quarkus is designed for density, but Kubernetes cannot scale what it cannot measure. The Horizontal Pod Autoscaler (HPA) relies entirely on the accuracy of your resource requests and limits. With traditional Spring Boot apps, engineers often over-provision memory to account for JVM heap uncertainty. With Quarkus native or even JVM mode, you can be far more precise. However, being too aggressive with limits causes OOMKills during GC spikes, while being too loose defeats the purpose of using Quarkus.

Define requests based on actual profiling

Never guess resource values. Use jcmd or Quarkus dev services to profile your application under load before setting production values. For a typical REST microservice in JVM mode, I typically start with these baselines and adjust:

resources:
  requests:
    memory: "256Mi"
    cpu: "250m"
  limits:
    memory: "512Mi"
    cpu: "1000m"

Configure HPA for Quarkus-specific behavior

Because Quarkus starts quickly, you can scale up faster than with traditional JVM apps. However, scaling down requires caution to avoid flapping. Configure stabilization windows to prevent rapid oscillation during traffic plateaus.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: quarkus-order-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 2
  maxReplicas: 20
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
        - type: Pods
          value: 4
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Pods
          value: 2
          periodSeconds: 120
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

The 30-second scale-up window leverages Quarkus's fast startup, allowing the cluster to react nearly instantly to spikes. The 5-minute scale-down window prevents premature termination during brief lulls. This asymmetry is key when you scale and monitor Quarkus in production because it aligns infrastructure behavior with application characteristics rather than generic defaults.

How do Quarkus native and JVM modes compare for production scaling?

Choosing between native and JVM mode is the most significant architectural decision for Quarkus operations. Native mode offers instant startup and minimal memory, but introduces build complexity and potential compatibility issues with reflection-heavy libraries. JVM mode retains full Java ecosystem compatibility at the cost of higher baseline resources. The right choice depends on your workload pattern, team expertise, and compliance requirements.

CriteriaQuarkus NativeQuarkus JVM Mode
Startup Time< 100ms (instant scaling)1–3 seconds (fast for JVM)
Memory Footprint50–150 MB RSS200–500 MB RSS
Peak Throughput10–20% lower initially (no JIT warmup)Higher after warmup (C2 compiler)
Build ComplexityHigh (GraalVM, reflection config)Low (standard JDK)
Library CompatibilityLimited (no dynamic proxies/reflection)Full Java ecosystem
Best ForServerless, edge, high-density clustersLong-running services, complex domains

In practice, I recommend JVM mode for most enterprise microservices unless you have a specific density or cold-start requirement. The operational overhead of maintaining GraalVM reachability metadata often outweighs the resource savings for services that run continuously. Reserve native mode for Lambda functions, Knative services, or edge deployments where every megabyte translates directly to cost savings. For teams in Nepal or emerging markets where cloud budgets are tight, native mode can reduce AWS/Azure bills by 40–60%, but only if your engineering team can absorb the debugging complexity.

Time After Start (seconds)Memory (MB)Native80 MBJVM350 MBSpring450 MB<0.1s startup~2s startup~8s startup
Memory and startup comparison informing decisions when you scale and monitor Quarkus in production

What are the common pitfalls when operating Quarkus at scale?

Even experienced teams stumble when transitioning from traditional Java to Quarkus. The framework's optimizations introduce new failure modes that don't exist in servlet containers. Recognizing these early prevents 3 AM pages and failed audits.

  • Ignoring connection pool sizing: Quarkus uses Agroal by default. In reactive mode, connections are non-blocking, but pool size still matters. Set quarkus.datasource.reactive.max-size based on your database's capacity, not CPU count. A common error is leaving this at the default (20) while running 50 pods, creating 1,000 DB connections that overwhelm PostgreSQL.
  • Missing health check differentiation: Quarkus exposes /q/health/live and /q/health/ready. Never use the same endpoint for both liveness and readiness probes in Kubernetes. Liveness should only fail if the process is deadlocked; readiness should fail if dependencies are unavailable. Mixing them causes cascading restarts during database blips.
  • Neglecting structured logging: Plain text logs are unparseable at scale. Enable JSON logging with quarkus.logging.json.enabled=true and integrate with your structured logging pipeline. This ensures trace IDs appear in every log line, correlating logs with traces automatically.
  • Overlooking TLS termination: Quarkus can handle TLS natively, but in Kubernetes, terminate at the ingress controller instead. Managing certificates inside pods adds operational burden and complicates rotation. Let cert-manager handle TLS at the edge and keep Quarkus focused on business logic.

Next Steps for Reliable Quarkus Operations

Successfully operating Quarkus requires treating observability and scaling as first-class concerns, not afterthoughts. Start by instrumenting your services with Micrometer and OpenTelemetry today, then validate your HPA configuration with load testing before trusting it in production. Remember that the goal when you scale and monitor Quarkus in production is predictable behavior under pressure, not just theoretical efficiency. If your team needs help designing audit-ready Quarkus infrastructure or optimizing existing deployments for cost and compliance, reach out to discuss your architecture.

Frequently Asked Questions

Yes, configure HorizontalPodAutoscaler targeting custom metrics from Micrometer. Ensure the native binary exposes /q/metrics and resource requests match actual memory usage to prevent OOM kills during scaling events in 2026 clusters.

Use Micrometer with Prometheus registry. Track jvm.memory.used and quarkus.native.heap bytes. Set alerts when heap exceeds eighty percent of container limits to catch leaks before pods restart unexpectedly.

Yes. Native binaries start in milliseconds versus seconds, allowing HPA to react instantly to traffic spikes without warmup penalties or over-provisioning buffers required by JVM-based frameworks.

Point readinessProbe and livenessProbe to /q/health/live and /q/health/ready. Set initialDelaySeconds to zero for native images since startup is sub-second, avoiding unnecessary probe failures during rapid scaling.

Yes. Add quarkus-opentelemetry extension and configure OTEL_EXPORTER_OTLP_ENDPOINT. Native mode supports tracing natively without bytecode agents, reducing overhead while maintaining full distributed trace context propagation across microservices.

Native images default to conservative heap sizing. Explicitly set -Xmx via JAVA_OPTS_APPEND matching container memory limits. Without this, the runtime allocates insufficient heap causing silent OOM exits unrelated to CPU pressure.

Pre-warm functions using provisioned concurrency or snapshotting. For Knative, set minScale to one. Native compilation already minimizes init time, but connection pooling and lazy bean initialization still add measurable latency.

Monitor http.server.requests duration p99, active database connections, thread pool saturation, and GC pause times. Combine with error rate thresholds to detect degradation before users notice performance issues in live traffic.

Most core extensions support native compilation. Verify compatibility in the Quarkus extension catalog before building. Some reflection-heavy libraries require manual configuration hints or may not work in native mode at all.

Enable async profiler via quarkus-async-profiler extension. Attach via JFR-compatible endpoints without stopping the process. Capture CPU and allocation profiles during peak load to identify hotspots invisible in development testing environments.

Use ubi-micro or distroless variants under fifty megabytes. Smaller images reduce pull times during scaling events and minimize attack surface. Avoid full JDK images since native binaries include only required runtime components.

Configure Agroal max-size based on pod count and DB limits. Use pgBouncer or RDS Proxy to multiplex connections. Without pooling middleware, each new pod opens fresh connections risking database exhaustion during autoscaling bursts.

Yes. Build native images on matching ARM64 runners. AWS Graviton and Azure Cobalt offer thirty percent better price-performance for Quarkus workloads compared to x86, especially for CPU-bound request processing tasks.

Restrict /q/* paths via network policies or ingress rules. Enable OIDC authentication for management interfaces. Never expose health or metrics publicly without RBAC, as they reveal internal topology and runtime state to attackers.

Native RSS includes code segments, thread stacks, and glibc overhead beyond Java heap. Measure true footprint with pmap or smem. Adjust container limits accounting for non-heap memory to avoid throttling or eviction.