Spring Boot Observability: Micrometer, Prometheus, Grafana Guide for Production
Learn Spring Boot observability with Micrometer, Prometheus, and Grafana.
20+ years shipping production Java in banking & fintech. Everything here is grounded in real deployments.
- ✓Java 17+ installed on your machine
- ✓Spring Boot 3.2.x project (we'll use 3.2.5 in examples)
- ✓Basic familiarity with REST APIs and Spring Boot Actuator
- ✓Docker installed for running Prometheus and Grafana locally
- ✓A payment-processing or similar domain example to follow along
• Micrometer acts as a metrics facade for Spring Boot, exposing JVM and custom metrics via Actuator endpoints • Prometheus scrapes those endpoints, storing time-series data with labels for multi-dimensional queries • Grafana visualizes Prometheus data with dashboards; use rate() and histogram_quantile() for real-world SLO tracking • Always add Micrometer registry in pom.xml: io.micrometer:micrometer-registry-prometheus:1.12.0 • Production gotcha: never expose /actuator/prometheus without authentication — use Spring Security or network policies
Think of observability like a car's dashboard. Micrometer is the sensor that measures engine temperature and speed. Prometheus is the data logger that records everything with timestamps. Grafana is the touchscreen display showing you real-time gauges and alerts. Without it, you're driving blind — your app could be overheating (high latency) and you'd only find out when users call support.
Observability isn't just buzzword bingo — it's the difference between knowing your app is dying at 3 AM and being woken up by a pager. I've been burned too many times by apps that looked healthy in logs but were silently leaking connections or burning CPU. Spring Boot 3.2.x with Micrometer 1.12.x gives you a first-class observability pipeline, but the official docs often skip the hard parts: what to actually measure, how to avoid metric explosion, and how to debug when Prometheus goes silent. In this guide, you'll move beyond the hello-world dashboards. We'll instrument a real payment-processing service, expose custom business metrics (like 'failed refunds per second'), and set up Prometheus rules that catch anomalies before they hit users. We'll also cover the production incident I had where a missing Maven dependency caused a silent metrics outage — and how we fixed it with a simple health check. By the end, you'll have a production-grade observability stack that actually helps you sleep at night.
Setting Up the Micrometer-Prometheus Pipeline
Let's start by instrumenting a Spring Boot 3.2.5 payment-processing service. First, add the required dependencies to your pom.xml. The key is spring-boot-starter-actuator plus the Micrometer Prometheus registry. Without the registry, /actuator/prometheus returns 404 — a silent failure we'll discuss later. The Micrometer core is auto-configured by Spring Boot, so you don't need explicit beans for basic JVM metrics. But for custom business metrics, you'll use MeterRegistry. Here's the minimal setup:
What the Official Docs Won't Tell You
The official Spring Boot docs show you how to enable Actuator and add the Prometheus dependency, but they skip the hard lessons. First, Micrometer uses dimensional metrics — every label combination creates a new time series. If you add a label like 'userId' to a counter, you'll create millions of series and blow up Prometheus storage. Second, the default http_server_requests_seconds metric includes a 'uri' label with path variables expanded (e.g., /api/orders/123). This is fine for low-cardinality paths, but if you have /api/orders/{id} with thousands of IDs, you'll get metric explosion. The fix is to use Spring's @Timed annotation with a custom tag that buckets the URI. Third, Prometheus scrape intervals matter — if your app has bursts of high-latency requests that last under 15 seconds, you'll miss them with a default 15s scrape. Set scrape_interval to 5s for critical services. Finally, the /actuator/prometheus endpoint returns metrics in Prometheus exposition format, which is text-based and easy to debug with curl. But the format is strict — any malformed metric name (e.g., starting with a digit) causes Prometheus to reject the entire scrape. I've debugged a production issue where a custom metric named '2xx_count' caused the whole endpoint to fail silently.
Instrumenting Business Metrics with Micrometer
Now let's add custom business metrics to a payment service. We'll track refund failures, payment processing time, and active transactions. Use MeterRegistry to create counters and timers. For the refund failure counter, we need to track by payment method (credit card, PayPal) and failure reason (insufficient funds, expired card). But remember the cardinality lesson — keep labels limited to 3-4 values. Here's a service class that records metrics:
Configuring Prometheus for Spring Boot Metrics
Prometheus needs a proper scrape configuration to discover your Spring Boot instances. In production with multiple replicas, use service discovery (e.g., Kubernetes pod annotations) instead of static targets. For local development, use docker-compose with a prometheus.yml file. Here's a production-grade configuration that includes relabeling for environment tags and rate limiting:
Building Grafana Dashboards for Real-Time Insights
Grafana dashboards are where observability becomes actionable. Start with a JVM dashboard using the Micrometer metrics. Use PromQL queries like rate(jvm_memory_used_bytes[5m]) for memory trends. For business metrics, create panels for refund failure rate per second and payment latency percentiles. Here's a key panel configuration for payment latency:
rate() to get per-second averages.Alerting Rules for Production Readiness
Prometheus alerting rules turn metrics into actionable notifications. Define rules in YAML and load them via the rule_files directive. For the payment service, we need alerts for: high refund failure rate, elevated p99 latency, and JVM heap usage > 80%. Here's a rule file that triggers alerts for these conditions:
Advanced: Custom Actuator Endpoint for Metrics Health
To prevent the 'silent metrics outage' scenario, create a custom Actuator health indicator that verifies Micrometer registries are active and metrics are being published. This runs alongside the default health check and gives you a separate signal. Here's an implementation:
Debugging Common Observability Issues
Even with proper setup, things go wrong. Here are the most common issues I've seen: (1) Prometheus returns 'invalid metric type' — check that your custom metrics use correct types (Counter, Gauge, Timer). A common mistake is using a Counter for values that can decrease (use Gauge instead). (2) Grafana shows 'No data' — verify Prometheus is scraping by checking the target page at /targets. If it shows 'DOWN', check network connectivity and authentication. (3) Metrics appear but with wrong labels — use Prometheus's /api/v1/labels endpoint to debug label values. For example, curl http://localhost:9090/api/v1/labels shows all label names. (4) High cardinality causing Prometheus OOM — use metric_relabel_configs to drop high-cardinality labels at scrape time. Here's a relabel config that drops 'userId' labels:
The Silent Metrics Outage: When Prometheus Stopped Scraping at 3 AM
- Always verify that /actuator/prometheus returns data after deployment — not just that the endpoint exists.
- Never assume the metrics pipeline is working because Actuator health says UP.
- Add a Prometheus scrape health check in your CI/CD pipeline to catch missing dependencies early.
record() method. Look for malformed metric names (starting with digit).curl -u monitoring:secret http://localhost:8080/actuator/prometheus | head -20curl http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job=="payment-service") | .health'| File | Command / Code | Purpose |
|---|---|---|
| pom.xml | Setting Up the Micrometer-Prometheus Pipeline | |
| PrometheusScrapeConfig.yml | scrape_configs: | What the Official Docs Won't Tell You |
| PaymentService.java | @Service | Instrumenting Business Metrics with Micrometer |
| docker-compose.yml | version: '3.8' | Configuring Prometheus for Spring Boot Metrics |
| GrafanaPanelQuery.json | { | Building Grafana Dashboards for Real-Time Insights |
| alerts.yml | groups: | Alerting Rules for Production Readiness |
| MetricsHealthIndicator.java | @Component | Advanced |
| prometheus.yml | scrape_configs: | Debugging Common Observability Issues |
Key takeaways
rate() on counters and histogram buckets in PromQL. Use histogram_quantile for latency percentiles. Always test queries in Prometheus's expression browser.Interview Questions on This Topic
How does Micrometer integrate with Spring Boot Actuator to expose Prometheus metrics?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Everything here is grounded in real deployments.
That's Spring Boot. Mark it forged?
3 min read · try the examples if you haven't