Hello

I tried new version of spring-framework 3.2.0-RC1 with spring.threads.virtual.enabled=true feature. But I faced issue with jar provided by bootJar: java.lang.UnsupportedOperationException: Virtual threads not supported on JDK <21 I prepared minimal reproducible example:

Environment: openjdk 21 2023-09-19 OpenJDK Runtime Environment (build 21+35) OpenJDK 64-Bit Server VM (build 21+35, mixed mode, sharing)

Project generated by https://start.spring.io/ with options: Project: Gradle - Groovy Language: Java Spring Boot: 3.2.0-RC1 Packaging: Jar Java: 21

DemoApplication.java contents:

package com.example.demo;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.task.VirtualThreadTaskExecutor;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        new VirtualThreadTaskExecutor().getVirtualThreadFactory();
        System.out.println("Hello world");
    }

}

Output for ./gradlew bootRun:

Welcome to Gradle 8.3!

Here are the highlights of this release:
 - Faster Java compilation
 - Reduced memory usage
 - Support for running on Java 20

For more details see https://docs.gradle.org/8.3/release-notes.html


> Task :bootRun
Hello world

BUILD SUCCESSFUL in 1s
4 actionable tasks: 3 executed, 1 up-to-date

Output for ./gradlew bootJar && java -jar ./build/libs/demo-0.0.1-SNAPSHOT.jar:

BUILD SUCCESSFUL in 1s
4 actionable tasks: 1 executed, 3 up-to-date
Exception in thread "main" java.lang.reflect.InvocationTargetException
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:118)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:91)
        at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:53)
        at org.springframework.boot.loader.launch.JarLauncher.main(JarLauncher.java:54)
Caused by: java.lang.UnsupportedOperationException: Virtual threads not supported on JDK <21
        at org.springframework.core.task.VirtualThreadDelegate.<init>(VirtualThreadDelegate.java:32)
        at org.springframework.core.task.VirtualThreadTaskExecutor.<init>(VirtualThreadTaskExecutor.java:41)
        at com.example.demo.DemoApplication.main(DemoApplication.java:10)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        ... 4 more

Comment From: jhoeller

Spring's actual Virtual Threads support lives in a multi-release jar (https://openjdk.org/jeps/238), with alternative versions of those classes included for JDK 21+. This works fine for us in common setups. Is this somehow not being picked up in your scenario?

Comment From: Hartigan

But I build project with jdk 21 and run project on jdk 21. Should I create multi-release jar only java 21 environment?

Comment From: philwebb

This looks like a regression with our new nested jar loader. For now you can add the following to your build to roll-back to the older implementation:

bootJar {
  loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC
}

Comment From: Hartigan

Yes, this solution works. Is my task a duplicate? Should I close it?

Comment From: philwebb

@Hartigan No thanks, we need to fix it in Boot. We'll transfer the issue or create a new one and close this one ourselves.

Comment From: philwebb

I bit of digging shows the assumption that the URLClassLoader will append #runtime to URLs so that they are opened with the correct runtime version set isn't true.

The code in jdk.internal.loader.URLClassPath does have some logic to do that, but only for JarLoader. We end up with the vanilla Loader due to this check.

I think we need to update our archive URLs to include #runtime.

Comment From: philwebb

Unfortunately appending #runtime to the URL is not enough because it is lost here