import java.util.ServiceLoader
interface A
data object B : A {
@JvmStatic
fun provider(): A = this
}
fun main() {
println(ServiceLoader.load(A::class.java).toList())
}
this program run just fine when ran standalone :
java.exe -p <program path>;<kotlin stdlib path> -m testJARSPI/my.program.MainKt
but produce an error when ran in a jar file:
java -jar <path to jar>
message :
Exception in thread "main" java.util.ServiceConfigurationError: my.program.A: my.program.B Unable to get public no-arg constructor
at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:586)
at java.base/java.util.ServiceLoader.getConstructor(ServiceLoader.java:679)
at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1240)
at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1273)
at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1309)
at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1393)
at kotlin.collections.CollectionsKt___CollectionsKt.toCollection(_Collections.kt:1295)
at kotlin.collections.CollectionsKt___CollectionsKt.toMutableList(_Collections.kt:1328)
at kotlin.collections.CollectionsKt___CollectionsKt.toList(_Collections.kt:1319)
at my.program.MainKt.main(Main.kt:15)
at my.program.MainKt.main(Main.kt)
Caused by: java.lang.NoSuchMethodException: my.program.B.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3641)
at java.base/java.lang.Class.getConstructor(Class.java:2324)
at java.base/java.util.ServiceLoader$1.run(ServiceLoader.java:666)
at java.base/java.util.ServiceLoader$1.run(ServiceLoader.java:663)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:569)
at java.base/java.util.ServiceLoader.getConstructor(ServiceLoader.java:674)
... 9 more
I cannot find what change that break when inside the jar file
NB: the MANIFEST.MF
file, the services
folder and the module-info.java
is in properly configured.
Was build using IntelliJ : Create JAR from Module -> extract to the target JAR
tested with jdk:
tried changing to a non final member but the error is still the same
data object B : A {
@JvmStatic
@Suppress("NON_FINAL_MEMBER_IN_OBJECT")
open // remove final modifier
fun provider(): A = this
}
Whenever the documentation of ServiceLoader
talks about provider methods1 it's always in the context of modules. Perhaps it could be clearer, but the implication is that a provider method will only be used when the service provider is found in a non-automatic named module. If the service provider is found on the class-path (i.e., the unnamed module) then the provider method, if there is one, will be ignored; only the provider constructor2 will be considered.
When you launch your application with:
java.exe -p <program path>;<kotlin stdlib path> -m testJARSPI/my.program.MainK
Your application is loaded from the module-path and resolved as a named module. Thus, the provider()
method works as you expect.
However, when you launch your application with:
java -jar <path to jar>
Your application is loaded from the class-path into the unnamed module3 and the provider()
method is ignored. So, it looks for the provider constructor, which must be public. But since your service provider is a Kotlin object
, its no-argument constructor is private. Hence the NoSuchMethodException
.
If you want your service provider to be a Kotlin object
, then I believe you're forced to place it on the module-path so that it is loaded into a named module. That way you can make use of the provider method to return the singleton instance.
If you want to be able to place your code on the class-path, then don't make the service provider a Kotlin object
. Though if you still want a singleton then you can introduce some indirection by making the service a "factory" that returns the singleton. Or just make sure to only load one instance of the service and pass it around as needed.
1. A "provider method" is a public, static, no-argument method named provider
.
2. A "provider constructor" is a public, no-argument constructor.
3. All code loaded from the class-path is put into the "unnamed module". Any module-info.class
file is ignored in this case (and none of its directives apply).
I'm confused, since the
module-info.java
makes it a named module should the provider method not work?
- comment
The module-info descriptor is ignored when the module is loaded from the class-path. This behavior is intentional; it helps non-modular code continue to work on Java 9+. The descriptor only has meaning when the module is loaded from the module-path. The -jar
option places the specified JAR file (and any dependencies listed in the Class-Path
manifest attribute) on the class-path, not the module-path.
Use the -p
/ --module-path
arguments to specify the module-path. Note you also have to make sure the module is resolved. That means the module needs to either be a root module, be directly or indirectly required by a root module, or provide a service used by a resolved module. Root modules are specified by the --add-modules
and -m
/ --module
arguments.