Visualizing Performance: A Complete Guide to Grafana Dashboards for Java Applications

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]
  1. Java Application: Instrumented to expose metrics (using Micrometer).
  2. Metrics Collector: Scrapes and collects metrics (Prometheus).
  3. Time-Series Database: Stores the metrics data (Prometheus's built-in TSDB).
  4. 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

  1. Navigate to Configuration > Data Sources
  2. Click Add data source
  3. Select Prometheus
  4. Set the URL: http://host.docker.internal:9090 (for Docker) or http://localhost:9090
  5. 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:

  1. Go to Dashboard Settings > Variables
  2. Click Add variable
  3. 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

  1. Organize Logically: Group related panels (JVM, HTTP, Database, Business).
  2. Use Meaningful Units: Format numbers appropriately (bytes, percentages, rates).
  3. Set Appropriate Y-Axis: Use fixed min/max for percentages (0-100).
  4. Color Coding: Use consistent colors (green=good, red=bad, yellow=warning).
  5. Dashboard Links: Create links between overview and detailed dashboards.
  6. Refresh Intervals: Set appropriate auto-refresh (30s for real-time, 5m for overview).
  7. 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:

  1. You instrument your Java app with Micrometer
  2. Collect metrics with Prometheus
  3. Visualize everything in Grafana with meaningful panels
  4. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper