Kibana Visualization for Java Application Metrics: A Complete Guide

Introduction

Kibana is a powerful data visualization tool that works seamlessly with Elasticsearch to transform your Java application metrics into actionable insights. When properly configured, it provides real-time visibility into application performance, JVM health, and business metrics.

This guide covers setting up comprehensive Kibana visualizations for Java application metrics using popular monitoring tools and frameworks.


Architecture Overview

[Java Application] → [Metrics Collector] → [Elasticsearch] → [Kibana Visualizations]
↓                  ↓                    ↓               ↓
Micrometer/          Logstash/           Data Store      Dashboards/
JMX Metrics        Elastic APM         & Search Engine   Visualizations

Step 1: Setting Up Metrics Export from Java

Maven Dependencies

<properties>
<micrometer.version>1.12.0</micrometer.version>
<spring-boot.version>3.2.0</spring-boot.version>
</properties>
<dependencies>
<!-- Spring Boot Actuator for metrics -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Micrometer Registry for Elastic -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-elastic</artifactId>
<version>${micrometer.version}</version>
</dependency>
<!-- Alternatively: Prometheus registry -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${micrometer.version}</version>
</dependency>
</dependencies>

Application Configuration

application.yml

management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
enabled: true
metrics:
export:
elastic:
enabled: true
host: http://localhost:9200
index: java-metrics
index-date-format: yyyy-MM-dd
step: 30s
distribution:
percentiles-histogram:
http.server.requests: true
percentiles:
http.server.requests: 0.5, 0.95, 0.99
sla:
http.server.requests: 100ms, 500ms, 1s
# Custom application metrics
app:
metrics:
prefix: myapp

Custom Metrics Configuration

MetricsConfig.java

package com.example.monitoring.config;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Timer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
@Configuration
public class MetricsConfig {
private final Map<String, Double> businessMetrics = new ConcurrentHashMap<>();
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config()
.commonTags("application", "order-service", 
"environment", "production",
"region", "us-east-1");
}
@Bean
public Gauge businessMetricsGauge(MeterRegistry registry) {
return Gauge.builder("app.business.active_orders")
.description("Number of active orders")
.register(registry, this, 
config -> businessMetrics.getOrDefault("active_orders", 0.0));
}
@Bean 
public Counter orderCreatedCounter(MeterRegistry registry) {
return Counter.builder("app.business.orders.created")
.description("Total orders created")
.register(registry);
}
public void setBusinessMetric(String key, Double value) {
businessMetrics.put(key, value);
}
}

Instrumented Service Example

OrderService.java

package com.example.service;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class OrderService {
private final Counter orderCounter;
private final Timer orderProcessingTimer;
private final MetricsConfig metricsConfig;
public OrderService(MeterRegistry registry, MetricsConfig metricsConfig) {
this.orderCounter = registry.counter("app.orders.total");
this.orderProcessingTimer = Timer.builder("app.orders.processing.time")
.description("Order processing time")
.register(registry);
this.metricsConfig = metricsConfig;
}
public Order processOrder(OrderRequest request) {
// Using Timer.Sample to measure method execution time
Timer.Sample sample = Timer.start();
try {
// Business logic
Order order = createOrder(request);
orderCounter.increment();
// Update business metric
metricsConfig.setBusinessMetric("active_orders", 
getActiveOrdersCount());
return order;
} finally {
sample.stop(orderProcessingTimer);
}
}
private Order createOrder(OrderRequest request) {
// Simulate processing time
try {
Thread.sleep(100 + (long)(Math.random() * 100));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new Order(request);
}
private double getActiveOrdersCount() {
// Implementation to get active orders from database
return Math.random() * 1000; // Example value
}
}

Step 2: Elasticsearch Index Templates

Create Index Template for Java Metrics

Using Kibana Dev Tools:

PUT _index_template/java-metrics-template
{
"index_patterns": ["java-metrics-*"],
"template": {
"mappings": {
"properties": {
"@timestamp": {
"type": "date"
},
"application": {
"type": "keyword"
},
"environment": {
"type": "keyword"
},
"metric.name": {
"type": "keyword"
},
"metric.value": {
"type": "double"
},
"jvm.memory.used": {
"type": "double"
},
"jvm.memory.max": {
"type": "double"
},
"jvm.gc.pause": {
"type": "double"
},
"http.server.requests.duration": {
"type": "double"
},
"system.cpu.usage": {
"type": "double"
}
}
},
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}
}

Step 3: Kibana Visualizations

1. JVM Memory Dashboard

Kibana Lens Visualization for Memory Usage:

{
"title": "JVM Memory Usage",
"type": "lens",
"attributes": {
"visualizationType": "lnsXY",
"title": "JVM Heap Memory Usage",
"references": [],
"state": {
"datasourceStates": {
"formBased": {
"layers": {
"layer1": {
"columns": {
"x-axis": {
"columnId": "x-axis",
"field": "@timestamp",
"dataType": "date",
"isBucketed": true,
"sourceField": "@timestamp",
"params": {
"interval": "auto"
}
},
"y-axis": {
"columnId": "y-axis",
"field": "jvm.memory.used",
"dataType": "number",
"isBucketed": false,
"sourceField": "jvm.memory.used",
"params": {
"format": {
"id": "bytes"
}
}
},
"split": {
"columnId": "split",
"field": "application",
"dataType": "string",
"isBucketed": true,
"sourceField": "application"
}
}
}
}
}
}
}
}
}

2. Application Performance Overview

Create Visualization via Kibana UI:

  1. Go to Analytics > Dashboard > Create dashboard
  2. Add visualization > Lens
  3. Configure metrics:

HTTP Request Latency:

  • X-axis: @timestamp (Date Histogram)
  • Y-axis: http.server.requests.duration (Average)
  • Break down by: status (if available)

Request Rate:

  • Y-axis: http.server.requests.count (Count)

Error Rate:

  • Y-axis: Count of requests with status: 5xx

3. Custom Business Metrics Visualization

Orders Processing Dashboard:

{
"title": "Business Metrics - Orders",
"type": "lens",
"attributes": {
"visualizationType": "lnsMetric",
"title": "Active Orders",
"references": [],
"state": {
"datasourceStates": {
"formBased": {
"layers": {
"layer1": {
"columns": {
"metric-column": {
"columnId": "metric-column",
"field": "app.business.active_orders",
"dataType": "number",
"isBucketed": false,
"sourceField": "app.business.active_orders",
"params": {
"format": {
"id": "number"
}
}
}
}
}
}
}
}
}
}
}

Step 4: Kibana Query Examples

KQL (Kibana Query Language) Examples

# JVM metrics for specific application
metric.name: "jvm.memory.used" and application: "order-service"
# High latency requests
http.server.requests.duration > 1000
# Error analysis
status >= 500 and status < 600
# Memory pressure detection
jvm.memory.used / jvm.memory.max > 0.8
# Business metrics with time range
app.business.active_orders > 100 and @timestamp >= now-1h

Advanced Queries with Aggregations

GET java-metrics-*/_search
{
"size": 0,
"query": {
"range": {
"@timestamp": {
"gte": "now-1h"
}
}
},
"aggs": {
"applications": {
"terms": {
"field": "application.keyword"
},
"aggs": {
"avg_memory": {
"avg": {
"field": "jvm.memory.used"
}
},
"p95_latency": {
"percentiles": {
"field": "http.server.requests.duration",
"percents": [95]
}
}
}
}
}
}

Step 5: Alerting and Anomaly Detection

Kibana Alert Rules

1. High Memory Usage Alert:

{
"rule_type_id": ".index-threshold",
"name": "High JVM Memory Usage",
"params": {
"index": "java-metrics-*",
"timeField": "@timestamp",
"aggType": "avg",
"aggField": "jvm.memory.used",
"groupBy": "top",
"termSize": 5,
"termField": "application.keyword",
"timeWindowSize": 5,
"timeWindowUnit": "m",
"thresholdComparator": ">",
"threshold": [8589934592],
"actions": []
}
}

2. High Error Rate Alert:

{
"rule_type_id": "monitoring_alert",
"name": "High HTTP Error Rate",
"params": {
"criteria": [
{
"aggType": "count",
"condition": ">",
"threshold": 100,
"filter": "status:5xx"
}
],
"index": "java-metrics-*",
"timeField": "@timestamp",
"timeWindowSize": 10,
"timeWindowUnit": "m"
}
}

Anomaly Detection Jobs

Create machine learning jobs in Kibana:

  1. Go to Analytics > Machine Learning > Anomaly Detection
  2. Create job > Single metric
  3. Configure:
  • Data source: java-metrics-*
  • Field: http.server.requests.duration
  • Partition field: application

Step 6: Complete Dashboard Example

Java Application Monitoring Dashboard

Create a comprehensive dashboard with:

  1. JVM Health Panel:
  • Heap Memory Usage (Area chart)
  • GC Pause Times (Line chart)
  • Thread Count (Metric)
  • CPU Usage (Gauge)
  1. Application Performance Panel:
  • Request Rate (Line chart)
  • Response Time Percentiles (Line chart)
  • Error Rate (Bar chart)
  • Endpoint Performance (Data table)
  1. Business Metrics Panel:
  • Active Orders (Metric)
  • Orders Created (Trend)
  • Processing Time (Gauge)
  1. System Resources Panel:
  • System CPU (Line chart)
  • Disk I/O (Area chart)
  • Network Usage (Line chart)

Best Practices

1. Metric Naming Convention

// Good naming
Counter.builder("app.orders.created")
Counter.builder("http.requests.total")
Gauge.builder("jvm.memory.heap.used")
// Avoid
Counter.builder("ordersCreated") // inconsistent casing
Counter.builder("app_orders_created") // mixed separators

2. Tagging Strategy

registry.counter("http.requests", 
"method", "GET",
"status", "200", 
"uri", "/api/orders",
"application", "order-service");

3. Retention Policy

PUT _ilm/policy/java-metrics-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_size": "50gb",
"max_age": "1d"
}
}
},
"delete": {
"min_age": "30d",
"actions": {
"delete": {}
}
}
}
}
}

Conclusion

Setting up Kibana visualizations for Java metrics provides powerful insights into your application's performance and health. By combining:

  • Micrometer for metric collection
  • Elasticsearch for storage and search
  • Kibana for visualization and alerting

You create a comprehensive monitoring solution that helps you:

  • Detect performance issues proactively
  • Understand application behavior under load
  • Make data-driven decisions about scaling and optimization
  • Quickly troubleshoot production incidents

The key to success is consistent metric naming, proper tagging, and designing visualizations that provide actionable information for your specific use case.

Leave a Reply

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


Macro Nepal Helper