jlink: service binding links many unnecessary modules - java

I face the problem that the service binding option of jlink links many, many modules, none of them seems to be necessary. These modules aren't linked when the service binding option is omitted.
Questions:
Q1: Do you see the same behavoir in your environment ?
Q2: Is this a bug or a desired behavoir ?
Q3: Why all these modules are linked ?
My application: The application is a simple service consisting of an interface, a provider and a consumer, each packed into a separate module, called modService, modProvider, modConsumer (details below).
OS: Windows 10
Jlink without --bind-services yields the expected result:
jlink --module-path "mods;%JAVA_HOME%\jmods"
--add-modules modConsumer
--output myRuntime
java --list-modules
java.base#9
modConsumer
modService
When the --bind-services option is applied, I would expect that in addition the module modProvider should be linked. However, see what happens (the three custom modules are at the end):
jlink --module-path "mods;%JAVA_HOME%\jmods"
--bind-services
--add-modules modConsumer
--output myRuntime
java --list-modules
java.base#9
java.compiler#9
java.datatransfer#9
java.desktop#9
java.logging#9
java.management#9
java.management.rmi#9
java.naming#9
java.prefs#9
java.rmi#9
java.scripting#9
java.security.jgss#9
java.security.sasl#9
java.smartcardio#9
java.xml#9
java.xml.crypto#9
jdk.accessibility#9
jdk.charsets#9
jdk.compiler#9
jdk.crypto.cryptoki#9
jdk.crypto.ec#9
jdk.crypto.mscapi#9
jdk.deploy#9
jdk.dynalink#9
jdk.internal.opt#9
jdk.jartool#9
jdk.javadoc#9
jdk.jdeps#9
jdk.jfr#9
jdk.jlink#9
jdk.localedata#9
jdk.management#9
jdk.management.cmm#9
jdk.management.jfr#9
jdk.naming.dns#9
jdk.naming.rmi#9
jdk.scripting.nashorn#9
jdk.security.auth#9
jdk.security.jgss#9
jdk.unsupported#9
jdk.zipfs#9
modConsumer
modProvider
modService
I have no clue why all these modules are linked because the provider just returns a string so that no other jdk module than java.base should be needed.
Below are the Java artifacts:
package test.service;
public interface HelloService {
public String sayHello();
}
package test.provider;
import test.service;
public class HelloProvider implements HelloService {
#Override public String sayHello() { return "Hello!"; }
}
package test.consumer;
import test.service;
import java.util.ServiceLoader;
public class HelloConsumer {
public static void main(String... args) {
ServiceLoader.load(HelloService.class).forEach(s -> System.out.println(s.sayHello()));
}
}
module modService {
exports test.service;
}
module modProvider {
requires modService;
provides test.service.HelloService with test.provider.HelloProvider;
}
module modConsumer {
requires modService;
uses test.service.HelloService;
}
Any help is appreciated.

Short version
Q1: Yes.
Q2: Desired behavior
Q3: Because you told jlink so with --bind-services 😉
Long Version
By default jlink does not bind services, to keep the created runtime as small as possible. That changes with --bind-services, about which the documentation says
Link service provider modules and their dependencies.
That mirrors the behavior during regular module resolution where, after all dependencies have been resolved, all modules that provide a service used by those modules are included in the readability graph.
The same happens in your case, so all modules providing services used by java.base, modConsumer, and modService are included in the image. As you have found out, that are quite a lot.
Solution
If you want to avoid that, you have to forego --bind-services and instead explicitly list the providers that you want to see in your image:
jlink --module-path "mods;%JAVA_HOME%\jmods"
--add-modules modConsumer,modProvider
--output myRuntime

As stated in the jlink documentation. The
--bind-services
Link service provider modules and their dependencies.
further, a sample in the same illustrates that the
option will link the modules resolved from root modules with service
binding; see the
Configuration.resolveAndBind
method.
From your previous command the root module and the ones resolved in your module graph by default are :
java.base#9
modConsumer
modService
and further, the other modules listed while making use of --bind-services flag are resolved via the java.base module.
I would expect that in addition the module modProvider should be
linked
As suggested by nicolai, you can add the provider module and ensure that it is resolved as well in the module graph.
--add-modules modConsumer,modProvider
Thinking out loud. 1. The current process for finding providers is iterative. 2. Is it possible to specify modules for which one to look for while finding service providers explicitly?

Related

Are there any JDK 11+ system modules which are not root modules?

In JDKs 9 and 10, there used to be a few modules such as java.xml.bind, containing Java EE classes. They were marked as deprecated and to be removed with the advent of JDK 9 and finally removed in 11 (see JEP 320). In a product I am contributing to, there used to be tests for the javac compiler option --add-modules, adding those modules as root modules. Those tests have been deactivated for JDK 11+. Instead of removing them, I would like to reactivate them, if there are any other JDK modules which are also non-root by default. The tests could then just use those modules instead.
I know I can just test --add-modules with my own modules, but then I have to specify them on the module path. The test case that an extra module path is not necessary for JDK modules added via --add-modules is also interesting, if any JDK 11+ modules still exist to be tested against. I am not talking about non-exported packages, but really about non-root JDK modules.
So, according to the information in this answer, I am actually looking for non-java.* modules among the system modules which do not export at least one package without qualification. In that case, those modules should not be root, and they would be eligible for my test case.
Update: What I am looking for is an equivalent for this in JDK 9:
import javax.xml.bind.JAXBContext;
public class UsesJAXB {
JAXBContext context;
}
xx> "C:\Program Files\Java\jdk-9.0.4\bin"\javac UsesJAXB.java
UsesJAXB.java:1: error: package javax.xml.bind is not visible
import javax.xml.bind.JAXBContext;
^
(package javax.xml.bind is declared in module java.xml.bind, which is not in the module graph)
1 error
xx> "C:\Program Files\Java\jdk-9.0.4\bin"\javac --add-modules java.xml.bind UsesJAXB.java
See? With --add-modules it builds, without it does not.
I am looking for modules (if any) in JDK 11-18, which would yield the same result when importing their classes in a simple program, i.e. require them to be explicitly added via --add-modules for compilation (not talking about runtime).
You can list all modules of the jdk with:
java --list-modules
Then you can print the module descriptors with:
java --describe-module a.module.name
After filterting these outputs in a little script, here are the modules of my JDK 17 than could qualify:
jdk.charsets
jdk.crypto.cryptoki
jdk.crypto.ec
jdk.editpad
jdk.internal.vm.compiler
jdk.internal.vm.compiler.management
jdk.jcmd
jdk.jdwp.agent
jdk.jlink
jdk.jpackage
jdk.localedata
jdk.zipfs
jdk.charsets, for instance, is a module providing a service.
Update after question update
So you are looking for a module that exports a package but is not in the default module graph when compiling a class in the unnamed module. According to the JEP, only java.* modules can qualify.
When I'm looking for modules required, either directly or indirectly, by java.se (which is a root module when it exists), I see all java.* modules of the JDK, but java.smartcardio. And due to some unknown magic, java.smartcardio is also in the default graph : I tried to compile a class importing one of its class and it works without --add-modules.
So I think you are down to using a non-java module exporting no package (like jdk.charsets), importing one of its class (like sun.nio.cs.ext.ExtendedCharsets) and either:
add --add-exports in addition to --add-modules when compiling your test class so that javac succeeds.
or parse the error message of javac and distinguish between "not in the module graph" and "not exported".

local system wide modulepath for Java11 services

I've defined an API like
package eu.ngong.api;
...
import java.util.ServiceLoader;
public interface API {
// some specified service operation signatures
...
static List<API> getInstances() {
ServiceLoader<API> services = ServiceLoader.load(API.class);
List<API> list = new ArrayList<>();
services.iterator().forEachRemaining(list::add);
return list;
}
Now I defined and tested a provider module implementing this API with a module-info.java like
module eu.ngong.mdl {
...
requires eu.ngong.api;
provides eu.ngong.api.API with eu.ngong.mdl.Mdl;
}
And tested and installed it in local repository on my machine.
Now I did a new maven project for an executable jar that I like to distribute to my colleagues, but without the service implementation (they do their own).
It exposes a module-info.java like
module eu.ngong.UI {
requires eu.ngong.api;
...
}
and I am going to test it using mvn test.
Without anything, it will lead to
ERROR eu.ngong.UI.UITest - run tests failed: no API module found
Sure, because that service module to satisfy the API is not on the modulepath - namely eu.ngong.mdl.Mdl.
What would be the best way to get the service provider module on the modulepath - to be active at mvn test time?
Best solution for me would be to put something in the local maven configuration file .m2/repository/settings - but I could not identify how.
Or I can put some option on the mvn test command line - this is also the first time I am trying to do that and would need a hint.

JPMS: How to let IntelliJ IDEA automatically discover and compile provider modules for a service module?

I have this project developed with IntelliJ IDEA. This project is a demo for Java 9 service modules. I followed the book Modular Programming in Java 9's Introducing Services chapter.
Source code
service module
module name: packt.sortutil
class full name: packt.util.SortUtil
public interface SortUtil {
<T extends Comparable> List<T> sortList(List<T> list);
}
2. provider module 1
module name: packt.sort.bubblesort
class full name: packt.util.impl.bubblesort.BubbleSortUtilImpl
class:
package packt.util.impl.bubblesort;
public class BubbleSortUtilImpl implements SortUtil
...
module-info.java:
module packt.sort.bubblesort {
requires packt.sortutil;
provides packt.util.SortUtil with packt.util.impl.bubblesort.BubbleSortUtilImpl;
}
Provider module 2 packt.sort.javasort is the same with the only difference of a different implementation.
consumer module
module-info.java:
module packt.addressbook {
requires packt.sortutil;
uses packt.util.SortUtil;
}
class:
Iterable<SortUtil> sortUtils = ServiceLoader.load(SortUtil.class);
for (SortUtil sortUtil : sortUtils){
System.out.println("Found an instance of SortUtil");
sortUtil.sortList(contacts);
}
The problem I found when running the Main class in consumer module is, provider1(packt.sort.bubblesort) and provider2(packt.sort.javasort) modules are not compiled. The reason is obvious: since instead of the service module reading the provider modules, it is the other way around. The compiler did not see the provider modules as no modules read them, so they are omitted in compilation.
I have 2 questions:
Is there a way to let IntelliJ IDEA automatically discover and compile provider modules for a service module(packt.sortutil in this case)?
Is there at least a way to manually add these 2 provider modules during compilation?
I tried add --add-modules packt.sort.bubblesort inside Run > Edit Configurations > VM Options as suggested by #nullpointer, but the arguments were added to java command. Since the module were not compiled, it shows Module packt.sort.bubblesort not found:
D:\programs\java\jdk-11.0.2\bin\java.exe --add-modules packt.sort.bubblesort "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.3\lib\idea_rt.jar=49375:C:\Program Files\JetBrains\IntelliJ IDEA 2018.3\bin" -Dfile.encoding=UTF-8 -p "D:\ebooks\Modular Programming in Java 9\practice-code\p205_Implementing_sorting_services\out\production\packt.addressbook;D:\ebooks\Modular Programming in Java 9\practice-code\p205_Implementing_sorting_services\out\production\packt.sortutil;D:\ebooks\Modular Programming in Java 9\practice-code\p205_Implementing_sorting_services\out\production\packt.contact" -m packt.addressbook/packt.addressbook.Main
Error occurred during initialization of boot layer
java.lang.module.FindException: Module packt.sort.bubblesort not found
Then I manually set the consumer module(packt.addressbook) to depend on the provider modules(packt.sort.bubblesort and packt.sort.javasort, highlighted in blue) inside IntelliJ IDEA(only in IDE, in terms of JPMS consumer module doesn't depend on the provider modules). It works all right now even though I am not sure it's the best solution.

Calling a Non Module Class from a Module Class in java 9

I am trying to call a non-module class from a module class. I have created a folder structure
moduledemo > allclasses > moduleC > packageC > MyMethods.class
is the path to my module class file
moduledemo > moduleC > packageC > MyMethods.java
and
moduledemo > nomodule > packageD > DemoNoModule.class
is the no module class that I am calling from MyMethods.java
I am able to compile the DemoNoModule file. I am able to compile MyMethods.java into allclasses folder moduleC.
When I am running MyMethods I am getting error moduleC not found. Can anyone update? I am using the following command to run
java --module-path allclasses -m moduleC/packageC.MyMethods
Both files code -> Non-Module Class
package packageD;
public class DemoNoModule {
public void showD() {
System.out.println("this is show of D in No Module");
}
}
Module class calling class
package packageC;
import packageD.*;
public class MyMethods {
public static void main(String s[]) {
DemoNoModule d=new DemoNoModule();
d.showD();
}
}
Module info in module C
module moduleC {
exports packageC;
}
On one hand, the moduleC(mind improving naming?) is a named module.
While on another, the "no module class" termed by you is nothing but as stated by Alan a class present on the classpath. The classes present on the classpath during the execution are part of an unnamed module in JPMS.
Quoting the documentation further:-
The unnamed module exports all of its packages. This enables
flexible migration... It does not, however, mean
that code in a named module can access types in the unnamed module. A
named module cannot, in fact, even declare a dependence upon the
unnamed module.
This is intentional to preserve the reliable configuration in the module system. As stated further :
If a package is defined in both a named module and the unnamed module
then the package in the unnamed module is ignored. This preserves
reliable configuration even in the face of the chaos of the class
path, ensuring that every module still reads at most one module
defining a given package.
Still, to make use of a class from the unnamed module in your named module moduleC, you can follow the suggestion of making use of the flag to add ALL-UNNAMED module to be read by modules on the module path using the
following command:
--add-reads <source-module>=<target-module> // moduleC=ALL-UNNAMED
As a special case, if the <target-module> is ALL-UNNAMED then
readability edges will be added from the source module to all present
and future unnamed modules, including that corresponding to the class
path.
PS: Do take into consideration the highlighted portion(above) of the documentation as you do so.
Also note the long-term solution would be to revise your design here, for which you can plan to move your code in the class DemoNoModule into an explicit module or package it separately to be converted into an automatic module.
Java 9 programs are supposed to be modular. That is how I understood jigsaw in JDK-9. So, IMHO, you'll have to 'wrap' your packageD in another module and in the module-info for moduleC write requires moduleD. Also moduleD should export packageD.
ALL-UNNAMED is added for backward compatibility, and I suppose it will be removed in some point of Java evolution.

Java 9 no class definition exception

So i want to try the http client
package com.company;
import jdk.incubator.http.HttpClient;
public class Main {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
}
}
And my module info looks like this
module com.company {
requires jdk.incubator.httpclient;
}
But i get java.lang.NoClassDefFoundError: jdk/incubator/http/HttpClient
And I don't really understand why. My java version is "build 9-ea+ 169" and I use the latest version of IntelliJ idea (2017.1.3). I looked into this answer and it looks like I have to just add requirement into a file, but it doesn't work for some reason.
works fine for me if I use --add-modules jdk.incubator.httpclient as the start-up parameter.
HttpClient client = HttpClient.newHttpClient();
client.executor().execute(() -> System.out.println("Here")); // prints Here
If you say that your module requires it, does not mean it will be included; at it is not included by default.
Either you or IntelliJ must have made a mistake. You are using an incubator module, about which the documentation says:
Incubator modules are part of the JDK run-time image produced by the standard JDK build. However, incubator modules are not resolved by default for applications on the class path. Applications on the class path must use the --add-modules command-line option to request that an incubator module be resolved. Applications developed as modules can specify requires or requires transitive dependences upon an incubator module directly.
I just confirmed that behavior on java-9-ea+169, i.e. I can compile and launch such a module (from the command line) without additional flags.
The fact that you do not get a compile error seems to indicate that IntelliJ correctly includes the module declaration in the compilation. The fact that you get a run-time error and that this answer helped indicates that the JVM does not see the code you launch as a module.
I ran into the same problems
java.lang.NoClassDefFoundError: jdk/incubator/http/HttpClient
with java-9-ea+173 and IntelliJ. I followed Eugenes and Nicolais advice to add jdk.incubator.httpclient explicitly to the module path via --add-modules jdk.incubator.httpclient in Run/Debug Configurations (on macOS: Menu Bar -> Run -> Edit Configurations -> Configuration Tab -> VM Options -> --add-modules jdk.incubator.httpclient
After that everything worked fine. Of course you have to add the dependency into the module-info.java like this as said before:
module network {
requires jdk.incubator.httpclient;
}
UPDATE:
With the latest IntelliJ IDEA 2017.2 EAP 172.2953.9 , I don't need to put the --add-modules to the VM Options. It just works out of the box.

Categories

Resources