Java Monitoring Tools: Beyond JMX (2025)


Java Monitoring Tools

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.

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.