micrometer-metrics/micrometer

Differences between monitoring ThreadPoolExecutor and ForkJoinPool

Open

#7,089 opened on Jan 16, 2026

View on GitHub
 (5 comments) (0 reactions) (0 assignees)Java (935 forks)batch import
enhancementhelp wanted

Repository metrics

Stars
 (4,220 stars)
PR merge metrics
 (Avg merge 1d 21h) (100 merged PRs in 30d)

Description

While I was reviewing https://github.com/micrometer-metrics/micrometer/pull/7083, I saw that if I use ExecutorServiceMetrics.monitor for a ThreadPoolExecutor (Executors.newFixedThreadPool) and a ForkJoinPool I get a few unexpected differences in the outputs.

executor.active and executor.queued have units (threads and tasks) for ThreadPoolExecutor while for ForkJoinPool they do not, this means that changing this:

- ExecutorServiceMetrics.monitor(registry, Executors.newFixedThreadPool(2), name, tags);
+ ExecutorServiceMetrics.monitor(registry, new ForkJoinPool(2), name, tags);

will result in different time series (executor_active_threads vs. executor_active) in the Prometheus output:

- executor_active_threads{name="test"} 0.0
+ executor_active{name="test"} 0.0
- executor_queued_tasks{name="test"} 0.0
+ executor_queued{name="test"} 0.0

(Fixing this is a breaking change.)

public class Demo {
    public static void main(String[] args) {
        generateAndPrintMetricsFor(Executors.newFixedThreadPool(2));
        generateAndPrintMetricsFor(new ForkJoinPool(2));
    }

    private static void generateAndPrintMetricsFor(ExecutorService executorServiceToMonitor) {
        PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
        ExecutorService executorService = ExecutorServiceMetrics.monitor(registry, executorServiceToMonitor, "test", Tags.empty());
        List<Future<?>> futures = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            futures.add(executorService.submit(Demo::sleep));
        }
        for (Future<?> future : futures) {
            try {
                future.get(1_000, TimeUnit.MILLISECONDS);
            }
            catch (Exception e) {
                System.out.println(e.getMessage());
            }
            finally {
                future.cancel(true);
            }
        }

        System.out.println("===== Metrics for " + executorService.getClass().getSimpleName());
        System.out.println(registry.scrape());
        executorService.shutdown();
    }

    private static void sleep() {
        try {
            Thread.sleep(100);
        }
        catch (InterruptedException e) {
            // noop
        }
    }
}

Contributor guide