Hello, I read the article in [1], which describes the problem that jlink cannot figure out which service providing modules should be included into the custom modular run-time image. In [2] there is a statement about this: Previously, jlink included all service providing modules. Now, none of them is included. Since I am not sure whether this was already discussed, I would like to suggest a third approach:
My suggestion is to add a multiplicity declaration to the module-info.java file for each uses clause. Here is an example: module com.socket { uses one com.socket.spi.NetworkSocketProvider; } This declares that the module `com.socket` requires exactly one implementation of the service `com.socket.spi.NetworkSocketProvider`. Here are further suggestions for multiplicity keywords: * optional - requires either zero or exactly one implementation * one - requires exactly one implementation * at least one - requires at least one implementation (this needs a better keyword) * any - any number of implementations is sufficient, even zero I suggest to require a multiplicity declaration, so the intent is always clear. However, it will also work to choose a default when no explicit declaration is given. Currently, any is the default. Adding a multiplicity declaration to the uses clause of the service consuming module allows to nicely solve the problem that is described in the article. Now, the jlink tool can check whether the multiplicity declaration is fulfilled. Then, it can include only the required service providing modules into the custom modular run-time image. Besides this declaration, one might add convenience methods to the ServiceLoader class. Currently, there is only ServiceLoader::iterator. I suggest to add the following methods: * loadOptional() - checks that there is exactly zero or one implementation and returns an Optional<> containing a new instance of the implementation, if any, throws a RuntimeException or Error if there is not exactly zero or one implementation * loadOne() - checks that there is exactly one implementation available and returns an instance of the implementation, throws a RuntimeException or Error if there is not exactly one implementation * loadAtLeastOne() - checks that there is at least one implementation available and returns a Set of instances of the implementations, throws a RuntimeException or Error if there is not at least one implementation * loadAny() - returns a Set of instances of the available implementations I think these methods should throw an Error, since the condition can and should be checked at compile-time. Thus, it is a fatal error if the constraint is not met at run-time. Finally, it might be helpful to be able to explicitly declare which service implementations are provided to which service consumers. For example, given a service `S` with the implementations `S1`, `S2` and `S3` provided by the modules `M1`, `M2` and `M3`. `S` is used by the modules `SC1` and `SC2`. `SC1` requires exactly one implementation of `S` while `SC2` requires any number of implementations. Now, it is unclear which implementation should be used by which service consuming module. Currently, the service consuming module will decide this manually in the code by iterating over all provided implementations. I think this should be explicitly declared. For example, one might declare that for `SC1`, `S` is provided with the implementation `S1`, but for `SC2`, `S` is provided with the implementations `S2` and `S3`. The great thing about this explicit declaration is, that tools like jlink can use this information, so it becomes even more clear which modules are required at run-time. Provided that `SC1` and `SC2` are included in the custom modular run-time image, the service configuration given above implies that also `M1`, `M2` and `M3` have to be included in the custom modular run-time image. If the service configuration is changed so for `SC2`, `S` is only provided with the implementation `S1`, then only `M1` has to be included into the custom modular run-time image, but `M2` and `M3` can be omitted. Also, my suggestion is that this is an interpreted document instead of a compiled one, so it can easily be changed after a release. For example, if an application uses a logging service it would be helpful to be able to add a custom logging service implementation to the module path and simply change the implementation which is used by the application by changing the service configuration file. This also implies that there should be a tool to validate the service configuration via the command line. Here is a summary of the proposed changes: * add a multiplicity declaration to the uses clause * use the multiplicity declaration to validate the current configuration of service consumers and service providers * include all required service provider modules when executing jlink * add convenience methods to the ServiceLoader class which match the introduced multiplicity declarations * add an (interpreted) document to explicitly declare the service configuration * add a command-line tool to validate the service configuration I am happy to hear any feedback. If you have trouble understanding my suggestions, please don't hesitate to ask for clarification. Again, I am not sure whether this topic was already discussed. I was unable to find it. If it was, please feel free to ignore this mail and send me a link to the discussion. [1]: https://blog.codecentric.de/en/2016/01/java9-jigsaw-missing-piece/ [2]: https://twitter.com/mreinhold/status/665122968851382273 Regards Stefan Dollase