Java Monitoring Tools: Beyond JMX (2025)

Modern Java applications require comprehensive monitoring beyond traditional JMX. This guide explores advanced monitoring tools and techniques for modern Java applications.
Pro Tip: Modern monitoring tools provide better observability, metrics collection, and visualization capabilities.
Table of Contents
Prometheus
Note: Prometheus provides powerful metrics collection and querying capabilities.
Prometheus Configuration
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'java-app'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['localhost:8080']
basic_auth:
username: admin
password: secret
- job_name: 'jvm'
static_configs:
- targets: ['localhost:9090']
Custom Metrics
@Service
public class OrderMetrics {
private final Counter orderCounter;
private final Gauge orderValueGauge;
private final Histogram orderProcessingTime;
public OrderMetrics(MeterRegistry registry) {
this.orderCounter = Counter.builder("orders_total")
.description("Total number of orders")
.tag("type", "all")
.register(registry);
this.orderValueGauge = Gauge.builder("order_value",
this::getCurrentOrderValue)
.description("Current order value")
.register(registry);
this.orderProcessingTime = Histogram.builder("order_processing_seconds")
.description("Order processing time")
.register(registry);
}
public void recordOrder(double value) {
orderCounter.increment();
orderProcessingTime.record(0.5); // Example processing time
}
private double getCurrentOrderValue() {
// Implementation
return 100.0;
}
}
Grafana
Pro Tip: Grafana provides powerful visualization and dashboard capabilities.
Grafana Dashboard
{
"dashboard": {
"id": null,
"title": "Java Application Metrics",
"panels": [
{
"title": "JVM Memory Usage",
"type": "graph",
"datasource": "Prometheus",
"targets": [
{
"expr": "jvm_memory_used_bytes{area=\"heap\"}",
"legendFormat": "{{area}}"
}
]
},
{
"title": "Order Processing Rate",
"type": "graph",
"datasource": "Prometheus",
"targets": [
{
"expr": "rate(orders_total[5m])",
"legendFormat": "Orders/sec"
}
]
}
]
}
}
Custom Dashboard
@Configuration
public class GrafanaConfig {
@Bean
public DashboardExporter dashboardExporter() {
return new DashboardExporter()
.withTitle("Custom Java Dashboard")
.withPanel(new Panel()
.withTitle("Custom Metrics")
.withTarget(new Target()
.withExpr("custom_metric_total")
.withLegendFormat("{{type}}")));
}
}
Micrometer
Note: Micrometer provides a vendor-neutral metrics facade.
Micrometer Configuration
@Configuration
public class MicrometerConfig {
@Bean
public MeterRegistry registry() {
SimpleMeterRegistry registry = new SimpleMeterRegistry();
// Add Prometheus registry
registry.add(new PrometheusMeterRegistry(PrometheusConfig.DEFAULT));
// Add custom metrics
registry.gauge("custom.gauge", 42.0);
return registry;
}
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
Custom Metrics
@Service
public class ServiceMetrics {
private final MeterRegistry registry;
public ServiceMetrics(MeterRegistry registry) {
this.registry = registry;
// Counter example
Counter.builder("service.calls")
.description("Number of service calls")
.tag("service", "order")
.register(registry);
// Timer example
Timer.builder("service.duration")
.description("Service call duration")
.tag("service", "order")
.register(registry);
// Gauge example
Gauge.builder("service.active",
this::getActiveServices)
.description("Active services")
.register(registry);
}
private double getActiveServices() {
// Implementation
return 5.0;
}
}
Jaeger
Pro Tip: Jaeger provides distributed tracing capabilities.
Jaeger Configuration
@Configuration
public class JaegerConfig {
@Bean
public Tracer tracer() {
Configuration.SamplerConfiguration samplerConfig =
Configuration.SamplerConfiguration.fromEnv()
.withType("const")
.withParam(1);
Configuration.ReporterConfiguration reporterConfig =
Configuration.ReporterConfiguration.fromEnv()
.withLogSpans(true);
Configuration config = new Configuration("java-app")
.withSampler(samplerConfig)
.withReporter(reporterConfig);
return config.getTracer();
}
}
Distributed Tracing
@Service
public class OrderService {
private final Tracer tracer;
public OrderService(Tracer tracer) {
this.tracer = tracer;
}
public Order processOrder(Order order) {
Span span = tracer.buildSpan("process_order")
.withTag("order_id", order.getId())
.start();
try (Scope scope = tracer.scopeManager().activate(span)) {
// Process order
validateOrder(order);
Order processedOrder = saveOrder(order);
span.setTag("status", "success");
return processedOrder;
} catch (Exception e) {
span.setTag("error", true);
span.log(Map.of("error", e.getMessage()));
throw e;
} finally {
span.finish();
}
}
}
Best Practices
Note: Following monitoring best practices ensures effective observability.
Monitoring Best Practices
- Use appropriate monitoring tools
- Collect relevant metrics
- Set up proper alerts
- Implement proper logging
- Use proper visualization
- Enable proper tracing
- Monitor system resources
- Track business metrics
- Set up proper dashboards
- Implement proper retention
- Use proper sampling
- Enable proper security
- Monitor application health
- Track performance metrics
- Follow monitoring guidelines
Conclusion
Modern Java monitoring tools provide powerful capabilities for application observability. By understanding and utilizing these tools effectively, you can gain better insights into your application's behavior and performance.