In the world of microservices and distributed systems, understanding your application's behavior in real-time is not a luxury—it's a necessity. Grafana has emerged as the de facto standard for visualizing time-series data, allowing teams to create rich, interactive dashboards. For Java developers, this means transforming raw metrics from your JVM-based applications into actionable insights.
This article provides a comprehensive guide to setting up a Grafana dashboard for Java applications, covering the entire pipeline from application instrumentation to final visualization.
The Monitoring Architecture
A typical monitoring stack for a Java application looks like this:
[Java Application] -> [Metrics Collector] -> [Time-Series Database] -> [Grafana]
- Java Application: Instrumented to expose metrics (using Micrometer).
- Metrics Collector: Scrapes and collects metrics (Prometheus).
- Time-Series Database: Stores the metrics data (Prometheus's built-in TSDB).
- Grafana: Queries the database and visualizes the data.
Step 1: Instrumenting Your Java Application with Micrometer
Micrometer is the metrics instrumentation facade for the JVM, providing a vendor-neutral interface for recording metrics. It's the standard way to expose metrics from Spring Boot applications.
Dependencies (Maven)
<!-- Spring Boot Actuator - Provides production-ready features --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Micrometer Registry for Prometheus --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>
Configuration (application.yml)
management:
endpoints:
web:
exposure:
include: health, info, metrics, prometheus
endpoint:
prometheus:
enabled: true
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name} # Adds application label to all metrics
distribution:
percentiles-histogram:
"[http.server.requests]": true # Enables histogram for latency calculations
Custom Metrics Example
You can also create custom business metrics:
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final Counter ordersCreatedCounter;
private final Counter ordersFailedCounter;
public OrderService(MeterRegistry registry) {
ordersCreatedCounter = Counter.builder("orders.created")
.description("Number of orders successfully created")
.tag("application", "order-service")
.register(registry);
ordersFailedCounter = Counter.builder("orders.failed")
.description("Number of failed order creations")
.tag("application", "order-service")
.register(registry);
}
public void createOrder(Order order) {
try {
// Business logic
ordersCreatedCounter.increment();
} catch (Exception e) {
ordersFailedCounter.increment();
throw e;
}
}
}
Your application will now expose metrics at: http://localhost:8080/actuator/prometheus
Step 2: Setting Up Prometheus
Prometheus will scrape the metrics endpoint and store the time-series data.
Prometheus Configuration (prometheus.yml)
global: scrape_interval: 15s # How frequently to scrape targets evaluation_interval: 15s # How frequently to evaluate rules rule_files: # - "first_rules.yml" scrape_configs: - job_name: 'java-applications' metrics_path: '/actuator/prometheus' scrape_interval: 10s static_configs: - targets: ['host.docker.internal:8080'] # For Docker setup labels: group: 'production' application: 'order-service'
Running Prometheus with Docker
docker run -d \ -p 9090:9090 \ -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \ --name prometheus \ prom/prometheus
Prometheus will be available at: http://localhost:9090
Step 3: Installing and Configuring Grafana
Running Grafana with Docker
docker run -d \ -p 3000:3000 \ --name grafana \ grafana/grafana
Grafana will be available at: http://localhost:3000 (default login: admin/admin)
Adding Prometheus as a Data Source
- Navigate to Configuration > Data Sources
- Click Add data source
- Select Prometheus
- Set the URL:
http://host.docker.internal:9090(for Docker) orhttp://localhost:9090 - Click Save & Test
Step 4: Creating a Comprehensive Java Application Dashboard
Here are the essential panels for a Java application dashboard with their PromQL queries:
1. JVM Memory Usage
# Heap Memory Usage
jvm_memory_used_bytes{area="heap"}
# Max Heap Memory
jvm_memory_max_bytes{area="heap"}
Visualization: Stat panels or time series with percentage calculation:
# Heap Memory Usage Percentage
(jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"}) * 100
2. Garbage Collection Performance
# GC Pause Time rate(jvm_gc_pause_seconds_sum[2m]) / rate(jvm_gc_pause_seconds_count[2m]) # GC Collection Count rate(jvm_gc_pause_seconds_count[5m])
3. Thread States
# Thread States jvm_threads_states_threads
Visualization: Pie chart or stacked time series.
4. HTTP Request Metrics
# Request Rate per Second
rate(http_server_requests_seconds_count[2m])
# 95th Percentile Latency
histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[2m]))
# Error Rate (5xx responses)
rate(http_server_requests_seconds_count{status=~"5.."}[2m])
5. Database Connection Pool (HikariCP)
# Active Connections hikaricp_connections_active # Total Connections hikaricp_connections_max # Idle Connections hikaricp_connections_idle
6. Custom Business Metrics
# Order Creation Rate rate(orders_created_total[5m]) # Error Ratio rate(orders_failed_total[5m]) / rate(orders_created_total[5m])
7. System Resources
# CPU Usage rate(process_cpu_usage[2m]) * 100 # File Descriptors process_files_open_files
Step 5: Advanced Dashboard Features
Templating with Variables
Add dropdown filters to make your dashboard dynamic:
- Go to Dashboard Settings > Variables
- Click Add variable
- Configure:
Application Selector:
Name: application Type: Query Query: label_values(application) Refresh: On Time Range Change
Instance Selector:
Name: instance
Type: Query
Query: label_values(up{application=~"$application"}, instance)
Refresh: On Time Range Change
Now use these variables in your queries:
rate(http_server_requests_seconds_count{application=~"$application", instance=~"$instance"}[2m])
Alerting Rules in Prometheus
Create alert rules in Prometheus to trigger notifications:
rules/alerts.yml:
groups:
- name: java_app_alerts
rules:
- alert: HighMemoryUsage
expr: (jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"}) * 100 > 85
for: 5m
labels:
severity: warning
annotations:
summary: "High memory usage in {{ $labels.application }}"
description: "Memory usage is at {{ $value }}%"
- alert: HighErrorRate
expr: rate(http_server_requests_seconds_count{status=~"5.."}[2m]) / rate(http_server_requests_seconds_count[2m]) > 0.05
for: 2m
labels:
severity: critical
annotations:
summary: "High error rate in {{ $labels.application }}"
description: "Error rate is {{ $value }}%"
Update prometheus.yml to include the rule file:
rule_files: - "rules/alerts.yml"
Annotation Queries
Add deployment markers to correlate performance with deployments:
# Query annotations from a separate metric
deployment_events{application=~"$application"}
Best Practices for Effective Dashboards
- Organize Logically: Group related panels (JVM, HTTP, Database, Business).
- Use Meaningful Units: Format numbers appropriately (bytes, percentages, rates).
- Set Appropriate Y-Axis: Use fixed min/max for percentages (0-100).
- Color Coding: Use consistent colors (green=good, red=bad, yellow=warning).
- Dashboard Links: Create links between overview and detailed dashboards.
- Refresh Intervals: Set appropriate auto-refresh (30s for real-time, 5m for overview).
- Time Range Presets: Configure useful time ranges (1h, 6h, 24h, 7d).
Complete Docker Compose Setup
For easy local development, use this docker-compose.yml:
version: '3.8' services: prometheus: image: prom/prometheus ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - ./rules:/etc/prometheus/rules command: - '--config.file=/etc/prometheus/prometheus.yml' - '--web.enable-lifecycle' grafana: image: grafana/grafana ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin volumes: - grafana-storage:/var/lib/grafana volumes: grafana-storage:
Conclusion
Setting up Grafana dashboards for Java applications transforms operational data into actionable intelligence. By following this guide:
- You instrument your Java app with Micrometer
- Collect metrics with Prometheus
- Visualize everything in Grafana with meaningful panels
- Add advanced features like templating and alerting
This monitoring stack gives you deep visibility into both technical metrics (JVM performance, HTTP latency) and business metrics (order rates, error ratios), enabling you to proactively identify issues, optimize performance, and understand user behavior.
The key to success is starting with the essential metrics and gradually expanding your dashboards as you identify what's most important for your specific application and business needs.
Further Reading: Explore Loki for log aggregation, Tempo for distributed tracing, and Alertmanager for advanced notification routing to create a complete observability platform.