Micrometer added a new MeterBinder for virtual thread metrics via https://github.com/micrometer-metrics/micrometer/pull/5067 - documentation is at https://docs.micrometer.io/micrometer/reference/reference/jvm.html#_java_21_metrics. It is in a new module micrometer-java21. It would be nice if Spring Boot has auto-configuration for VirtualThreadMetrics.

Comment From: nosan

class file has wrong version 65.0, should be 61.0 Please remove or make sure it appears in the correct subdirectory of the classpath.

micrometer-java21 is built using JDK21, whereas Spring Boot source code is JDK17 source compatible, hence either reflection or multi-release jar should be used to add support for VirtualThreadMetrics.

I've prototyped changes in my branch by using reflection and FactoryBean.

I also checked VirtualThreadMetrics with a native-image, and unfortunately, I got the following exception:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'virtualThreadMetrics': Instantiation of supplied bean failed
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1239)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1176)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:288)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1122)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1093)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1030)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:318)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350)
        at task.gh43122.Gh43122Application.main(Gh43122Application.java:12)
        at java.base@21.0.5/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.lang.UnsatisfiedLinkError: jdk.jfr.internal.JVM.getPid()Ljava/lang/String; [symbol: Java_jdk_jfr_internal_JVM_getPid or Java_jdk_jfr_internal_JVM_getPid__]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.access.JNINativeLinkage.getOrFindEntryPoint(JNINativeLinkage.java:152)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.JNIGeneratedMethodSupport.nativeCallAddress(JNIGeneratedMethodSupport.java:54)
        at jdk.jfr@21.0.5/jdk.jfr.internal.JVM.getPid(Native Method)
        at jdk.jfr@21.0.5/jdk.jfr.internal.Repository.createRepository(Repository.java:110)
        at jdk.jfr@21.0.5/jdk.jfr.internal.Repository.setBasePath(Repository.java:65)
        at jdk.jfr@21.0.5/jdk.jfr.internal.Repository.ensureRepository(Repository.java:79)
        at jdk.jfr@21.0.5/jdk.jfr.internal.PlatformRecorder.<init>(PlatformRecorder.java:81)
        at jdk.jfr@21.0.5/jdk.jfr.FlightRecorder.getFlightRecorder(FlightRecorder.java:171)
        at jdk.jfr@21.0.5/jdk.jfr.Recording.<init>(Recording.java:107)
        at jdk.jfr@21.0.5/jdk.jfr.Recording.<init>(Recording.java:131)
        at jdk.jfr@21.0.5/jdk.jfr.consumer.RecordingStream.<init>(RecordingStream.java:108)
        at jdk.jfr@21.0.5/jdk.jfr.consumer.RecordingStream.<init>(RecordingStream.java:101)
        at io.micrometer.java21.instrument.binder.jdk.VirtualThreadMetrics.createRecordingStream(VirtualThreadMetrics.java:78)
        at io.micrometer.java21.instrument.binder.jdk.VirtualThreadMetrics.<init>(VirtualThreadMetrics.java:57)
        at io.micrometer.java21.instrument.binder.jdk.VirtualThreadMetrics.<init>(VirtualThreadMetrics.java:49)
        at task.gh43122.Gh43122Application.virtualThreadMetrics(Gh43122Application.java:17)
        at task.gh43122.Gh43122Application$$SpringCGLIB$$0.CGLIB$virtualThreadMetrics$0(<generated>)
        at task.gh43122.Gh43122Application$$SpringCGLIB$$FastClass$$1.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258)
        at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:348)
        at task.gh43122.Gh43122Application$$SpringCGLIB$$0.virtualThreadMetrics(<generated>)
        at task.gh43122.Gh43122Application__BeanDefinitions.lambda$getVirtualThreadMetricsInstanceSupplier$0(Gh43122Application__BeanDefinitions.java:32)
        at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63)
        at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51)
        at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$1(BeanInstanceSupplier.java:219)
        at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58)
        at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiateWithFactoryMethod(SimpleInstantiationStrategy.java:88)
        at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:256)
        at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:219)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:979)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1233)
        ... 20 more

Sample to reproduce: gh-43122.zip

mvn clean spring-boot:build-image -Pnative
docker run -p 8080:8080 docker.io/library/gh-43122:0.0.1-SNAPSHOT

Comment From: oleksiizaitsev7

Hi it can be done with some conditions as we have here. But I'm not sure if that is the right way or preferable strategy for the java 21 support

Comment From: nosan

Thanks for the suggestion, @oleksiizaitsev7 The problem is that using the VirtualThreadMetrics class results in a compile error, so adding @EnabledForJreRange won't resolve the issue.

Comment From: mhalbritter

Superseded by #43852.