Gradle 7.6, JDK 19 and Scala 3: "java.lang.ClassNotFoundException: scala.Predef$" even though scala-library-2.13.10.jar is in classpath

I’ve setup a small minimal example Gradle 7.6 project which uses JDK 19 and Scala. Compilation of Java and Scala is done without any errors, however as soon as I run the “application” the JVM is unable to find Scala’s Predef:

Exception in thread "main" java.lang.NoClassDefFoundError: scala/Predef$
        at eu.geekplace.beanstalk.core/eu.geekplace.beanstalk.core.scala.BeanstalkScala.sayHello(BeanstalkScala.scala:4)
        at eu.geekplace.beanstalk.app/eu.geekplace.beanstalk.app.App.main(App.java:7)
Caused by: java.lang.ClassNotFoundException: scala.Predef$
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        ... 2 more

even though, I believe Predef’s class is in classpath, since scala-library-2.13.10.jar, which apparently contains Predef, is in the classpath. Checked via invoking gradle with --debug, which shows the following arguments being passed to the JVM when executing the application

/opt/openjdk-bin-19.0.1_p10/bin/java \
  --enable-preview \
  -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant \
  --module-path /home/flo/repos/beanstalk/beanstalk-app/build/libs/beanstalk-app.jar:/home/flo/repos/beanstalk/beanstalk-core/build/libs/beanstalk-core.jar:/home/flo/.gradle/caches/modules-2/files-2.1/org.scala-lang/scala3-library_3/3.2.1/ddc77e1f365f0a5202dda84eefede515feb93784/scala3-library_3-3.2.1.jar:/home/flo/.gradle/caches/modules-2/files-2.1/org.scala-lang/scala-library/2.13.10/67c1afabaea9ba51321159e70a78515647e1b73d/scala-library-2.13.10.jar \
  --module eu.geekplace.beanstalk.app/eu.geekplace.beanstalk.app.App

The minimal non-working example can be found at

Why isn’t the JVM finding the class?

I investigated a little bit further. Somehow I assumed that all legacy non-module libraries are accessible by everything else. In particular other java modules. However this is not the case. All non-module libraries are put (by Gradle) in the classpath and form the so called unnamed module. This is true for the Scala library, as its jars do not contain a module-info.class. So the first step towards fixing this is to allow my libraries module access to the unnamed module. This is done via

--add-reads eu.geekplace.beanstalk.core=ALL-UNNAMED

when invoking the JVM.

However, there is still one issue, which is suspect to be a Gradle bug. Starting the application with

$ ./gradlew beanstalk-app:run

shows that Gradle invokes the JVM without the Scala jars put in the class path, instead they are added to the module path. However, Gradle’s documentation states

. Gradle will automatically put a Jar of your dependencies on the module path, instead of the classpath, if these three things are true:

  • The Jar our module depends on is itself a module, which Gradles decides based on the presence of a module-info.class — the compiled version of the module descriptor — in the Jar. (Or, alternatively, the presence of an Automatic-Module-Name attribute the Jar manifest)

since the last point is not true for Scala’s runtime libraries, they should not be provided via --module-path but via --class-path. However, this is the command, extracted from Gradle’s debug output, that gradle uses to execute the application:

flo@ubook beanstalk $ /opt/openjdk-bin-19.0.1_p10/bin/java \
    --enable-preview \
    --add-reads eu.geekplace.beanstalk.core=ALL-UNNAMED \
    -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant \
    --module-path /home/flo/repos/beanstalk/beanstalk-app/build/libs/beanstalk-app.jar:/home/flo/repos/beanstalk/beanstalk-core/build/libs/beanstalk-core.jar:/home/flo/.gradle/caches/modules-2/files-2.1/org.scala-lang/scala3-library_3/3.2.1/ddc77e1f365f0a5202dda84eefede515feb93784/scala3-library_3-3.2.1.jar:/home/flo/.gradle/caches/modules-2/files-2.1/org.scala-lang/scala-library/2.13.10/67c1afabaea9ba51321159e70a78515647e1b73d/scala-library-2.13.10.jar \
    --module eu.geekplace.beanstalk.app/eu.geekplace.beanstalk.app.App
WARNING: Using incubator modules: jdk.incubator.concurrent
Hello first
Hello second
Exception in thread "main" java.lang.NoClassDefFoundError: scala/Predef$
	at eu.geekplace.beanstalk.core/eu.geekplace.beanstalk.core.scala.BeanstalkScala.sayHello(BeanstalkScala.scala:4)
	at eu.geekplace.beanstalk.app/eu.geekplace.beanstalk.app.App.main(App.java:8)
Caused by: java.lang.ClassNotFoundException: scala.Predef$
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    ... 2 more

If I move Scala’s runtime libraries from --module-path to --class-path, then the application works as expected:

flo@ubook beanstalk $ /opt/openjdk-bin-19.0.1_p10/bin/java \
    --enable-preview \
    --add-reads eu.geekplace.beanstalk.core=ALL-UNNAMED \
    -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant \
    --module-path /home/flo/repos/beanstalk/beanstalk-app/build/libs/beanstalk-app.jar:/home/flo/repos/beanstalk/beanstalk-core/build/libs/beanstalk-core.jar \
    --class-path /home/flo/.gradle/caches/modules-2/files-2.1/org.scala-lang/scala3-library_3/3.2.1/ddc77e1f365f0a5202dda84eefede515feb93784/scala3-library_3-3.2.1.jar:/home/flo/.gradle/caches/modules-2/files-2.1/org.scala-lang/scala-library/2.13.10/67c1afabaea9ba51321159e70a78515647e1b73d/scala-library-2.13.10.jar
    --module eu.geekplace.beanstalk.app/eu.geekplace.beanstalk.app.App
WARNING: Using incubator modules: jdk.incubator.concurrent
Hello first
Hello second
Hello World from Scala

The source code of the minimal non-working project can be found at

Is there something wrong with my project setup, or is this a bug in Gradle?

FWIW, since I believe this is an issue in Gradle, I’ve created

Your assumption is wrong.
This one:

since the last point is not true for Scala’s runtime libraries, they should not be provided via --module-path but via --class-path.

Of course it is true: