Back to Journal
DevOps

Complete Guide to Monitoring & Observability with Java

A comprehensive guide to implementing Monitoring & Observability using Java, covering architecture, code examples, and production-ready patterns.

Muneer Puthiya Purayil 18 min read

Java's monitoring ecosystem is built on decades of enterprise observability tooling. From JMX to Micrometer to the OpenTelemetry Java agent, the JVM provides the deepest application introspection of any mainstream runtime. This guide covers instrumenting Java services for production monitoring on modern infrastructure.

Micrometer: The Metrics Standard

Micrometer is the SLF4J of metrics — a vendor-neutral facade that supports Prometheus, Datadog, New Relic, and 20+ backends.

java
1@Configuration
2public class MetricsConfig {
3 
4 @Bean
5 public MeterRegistryCustomizer<MeterRegistry> commonTags() {
6 return registry -> registry.config()
7 .commonTags("service", "order-service", "region", System.getenv("AWS_REGION"));
8 }
9 
10 @Bean
11 public TimedAspect timedAspect(MeterRegistry registry) {
12 return new TimedAspect(registry);
13 }
14}
15 
16@Service
17public class OrderService {
18 private final MeterRegistry registry;
19 private final Counter ordersCreated;
20 private final Timer orderProcessing;
21 private final DistributionSummary orderAmount;
22 
23 public OrderService(MeterRegistry registry) {
24 this.registry = registry;
25 this.ordersCreated = Counter.builder("orders.created.total")
26 .description("Total orders created")
27 .register(registry);
28 this.orderProcessing = Timer.builder("orders.processing.duration")
29 .description("Order processing duration")
30 .publishPercentiles(0.5, 0.95, 0.99)
31 .publishPercentileHistogram()
32 .register(registry);
33 this.orderAmount = DistributionSummary.builder("orders.amount")
34 .description("Order amount in cents")
35 .baseUnit("cents")
36 .publishPercentiles(0.5, 0.95)
37 .register(registry);
38 }
39 
40 @Timed(value = "orders.create", description = "Create order duration")
41 public Order createOrder(CreateOrderRequest request) {
42 return orderProcessing.record(() -> {
43 Order order = processOrder(request);
44 ordersCreated.increment();
45 orderAmount.record(order.getAmountCents());
46 return order;
47 });
48 }
49}
50 

Spring Boot Actuator

yaml
1management:
2 endpoints:
3 web:
4 exposure:
5 include: health,prometheus,info,metrics
6 endpoint:
7 health:
8 show-details: when-authorized
9 probes:
10 enabled: true
11 group:
12 readiness:
13 include: db,redis,diskSpace
14 liveness:
15 include: ping
16 metrics:
17 export:
18 prometheus:
19 enabled: true
20 distribution:
21 percentiles-histogram:
22 http.server.requests: true
23 slo:
24 http.server.requests: 50ms,100ms,200ms,500ms
25 tags:
26 application: ${spring.application.name}
27 

Spring Boot Actuator exposes JVM metrics automatically: heap usage, GC pauses, thread counts, class loading, and HTTP server metrics. The /actuator/prometheus endpoint provides all metrics in Prometheus format.

JVM-Specific Monitoring

Critical JVM Metrics

java
1@Component
2public class JvmMetricsCollector {
3 
4 @Autowired
5 private MeterRegistry registry;
6 
7 @PostConstruct
8 public void registerCustomJvmMetrics() {
9 // Memory pool utilization
10 Gauge.builder("jvm.memory.pool.utilization", () -> {
11 MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
12 MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
13 return (double) heapUsage.getUsed() / heapUsage.getMax();
14 }).register(registry);
15 
16 // Thread state distribution
17 for (Thread.State state : Thread.State.values()) {
18 Gauge.builder("jvm.threads.state", () -> {
19 return Thread.getAllStackTraces().keySet().stream()
20 .filter(t -> t.getState() == state)
21 .count();
22 }).tag("state", state.name().toLowerCase())
23 .register(registry);
24 }
25 }
26}
27 

GC Monitoring Alerts

yaml
1groups:
2 - name: jvm-alerts
3 rules:
4 - alert: HighGCPauseTime
5 expr: rate(jvm_gc_pause_seconds_sum[5m]) / rate(jvm_gc_pause_seconds_count[5m]) > 0.5
6 for: 10m
7 labels:
8 severity: warning
9 annotations:
10 summary: "Average GC pause > 500ms in {{ $labels.application }}"
11 
12 - alert: HeapMemoryHigh
13 expr: jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.85
14 for: 10m
15 labels:
16 severity: warning
17 
18 - alert: ThreadCountHigh
19 expr: jvm_threads_live_threads > 500
20 for: 5m
21 labels:
22 severity: warning
23 

Need a second opinion on your DevOps pipelines architecture?

I run free 30-minute strategy calls for engineering teams tackling this exact problem.

Book a Free Call

OpenTelemetry Java Agent

The OTel Java agent provides zero-code instrumentation via bytecode manipulation:

bash
1java -javaagent:opentelemetry-javaagent.jar \
2 -Dotel.service.name=order-service \
3 -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
4 -Dotel.metrics.exporter=prometheus \
5 -Dotel.traces.sampler=parentbased_traceidratio \
6 -Dotel.traces.sampler.arg=0.1 \
7 -jar app.jar
8 

The agent automatically instruments: HTTP clients/servers, JDBC, JMS, Kafka, gRPC, Redis, and 100+ libraries. No code changes required.

Distributed Tracing

java
1@RestController
2@RequestMapping("/api/v1/orders")
3public class OrderController {
4 
5 private final Tracer tracer;
6 private final OrderService orderService;
7 
8 public OrderController(Tracer tracer, OrderService orderService) {
9 this.tracer = tracer;
10 this.orderService = orderService;
11 }
12 
13 @PostMapping
14 public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
15 Span span = tracer.spanBuilder("create-order")
16 .setAttribute("order.product_id", request.getProductId())
17 .setAttribute("order.quantity", request.getQuantity())
18 .startSpan();
19 
20 try (Scope scope = span.makeCurrent()) {
21 Order order = orderService.create(request);
22 span.setAttribute("order.id", order.getId());
23 return ResponseEntity.status(HttpStatus.CREATED).body(order);
24 } catch (Exception e) {
25 span.setStatus(StatusCode.ERROR);
26 span.recordException(e);
27 throw e;
28 } finally {
29 span.end();
30 }
31 }
32}
33 

Conclusion

Java's monitoring story is mature and comprehensive. Micrometer provides vendor-neutral metrics with rich histogram and percentile support. Spring Boot Actuator exposes health, metrics, and diagnostics out of the box. The OpenTelemetry Java agent adds distributed tracing without code changes. And JMX provides deep JVM introspection that no other runtime matches.

The key Java-specific monitoring focus areas are GC behavior (pause frequency and duration), heap utilization trends (for capacity planning), and thread state distribution (for detecting deadlocks and thread pool saturation). These JVM metrics complement application-level RED metrics to provide complete service observability.

FAQ

Need expert help?

Building with CI/CD pipelines?

I help teams ship production-grade systems. From architecture review to hands-on builds.

Muneer Puthiya Purayil

SaaS Architect & AI Systems Engineer. 10+ years shipping production infrastructure across fintech, automotive, e-commerce, and healthcare.

Engage

Start a
Conversation.

For teams building at scale: SaaS platforms, agentic AI systems, and enterprise mobile infrastructure. Scope and fit are evaluated before any engagement begins.

Limited availability · Q3 / Q4 2026