Java 9 Jigsaw – A Missing Piece

No Comments

Some excellent blog posts have already been written about Java 9 – Jigsaw, not the least by my colleague Florian Troßbach in two parts here; part 1 and here; part 2.
During this post I would like to dive deeper into some lesser known details and quirks, specifically a RuntimeException thrown after incorrectly building a
custom modular run-time image.

Service Resolution

Since Java SE 6 already, the platform provides the ServiceLoader API to extend existing applications. The ServiceLoader makes it possible to detect existing implementations of a certain interface / abstract class and load them to be used. This solution still works nicely with Java modules. Where previously the JAR-files had to be present on the class-path, it is now sufficient to have the modules present on the module-path. See the following example.

The NetworkSocketProvider is our abstract class that needs to be implemented by the various instances we would like to lookup:

public abstract class NetworkSocketProvider {
    protected NetworkSocketProvider() {}
 
    public abstract NetworkSocket openNetworkSocket();
}

The FastNetworkSocketProvider provides one implementation, it will create a FastNetworkSocket for us. Naturally, we could have multiple implementations.

public class FastNetworkSocketProvider extends NetworkSocketProvider {
    @Override
    public NetworkSocket openNetworkSocket() {
        return new FastNetworkSocket();
    }
}

And now for the code that will do the lookup:

public static NetworkSocket open() {
    ServiceLoader sl = ServiceLoader.load(NetworkSocketProvider.class);
 
    Iterator iter = sl.iterator();
 
    if (!iter.hasNext()) {
        throw new RuntimeException("No service providers found");
    }
 
    NetworkSocketProvider provider = iter.next();
    for (NetworkSocketProvider networkSocketProvider : sl) {
        System.out.println(networkSocketProvider.openNetworkSocket().getClass());
    }
    return provider.openNetworkSocket();
}

This static method will, using the ServiceLoader, give us a list of possible implementations and in this case simply return the first one found.

Module Resolution

While service resolution provides not much new, something that is new, is the Layer API and related classes. The Layer API is available in the java.lang.reflect package and represents a ‘layer’ of modules. The following code makes it possible to get a list of loaded modules, specifically those with names starting with “nl.codecentric”:

private static void printModules() {
    Layer layer = Layer.boot();
    layer.modules().stream().filter(m -> m.getName().startsWith("nl.codecentric")).forEach(m -> {
        String name = m.getName();
        Optional version = m.getDescriptor().version();
        System.out.println("Loaded module: " + name + " - version: " + version);
    });
}

Run-time Image – A Missing Piece

Java 9 allows for custom modular run-time images to be created as described by Florian here. What’s amazing about the JLink utility is that it will resolve all required modules automatically. But there’s also a catch.

Given the above example with our sockets, we have three modules:

src/
├── nl.codecentric.test
├── nl.codecentric.socket
├── nl.codecentric.fastsocket

The test module contains the Main method and will request a socket. The socket module contains the abstract NetworkSocketProvider and the class to resolve the socket implementations. Finally the fastsocket module contains the FastNetworkSocketProvider and FastNetworkSocket implementations.

When compiling these modules and running with the following command (with all modules on the module-path):

java -mp mlib -m nl.codecentric.test

We get the following output:

Loaded module: nl.codecentric.socket - version: Optional[1.0]
Loaded module: nl.codecentric.fastsocket - version: Optional[2.0]
Loaded module: nl.codecentric.test - version: Optional.empty
class nl.codecentric.fastsocket.FastNetworkSocket
 
class nl.codecentric.fastsocket.FastNetworkSocket version: 2.0

However, if we now create a custom modular run-time image from this using the command:

jlink --modulepath $JAVA_HOME/jmods:mlib --addmods nl.codecentric.test --output linkout

And running this will generate the following output:

Loaded module: nl.codecentric.test - version: Optional.empty
Loaded module: nl.codecentric.socket - version: Optional[1.0]
Exception in thread "main" java.lang.RuntimeException: No service providers found
	at nl.codecentric.socket.NetworkSocket.open(nl.codecentric.socket@1.0/NetworkSocket.java:20)
	at nl.codecentric.test.Main.main(nl.codecentric.test@/Main.java:15)

As you see, we’ll get a RuntimeException because it cannot find the FastNetworkSocketProvider. Listing the packaged modules:

./bin/java -listmods

Will only show:

java.base@9.0
nl.codecentric.socket@1.0
nl.codecentric.test

What happened?? While the JLink util will resolve all direct references and dependencies, it will not link providers. No module requires the fastsocket module, and thus it is not included. While this is somewhat logical, the risk is that this issue only shows up during run-time. And might be hard to debug even.

The solution is to include all required modules explicitly:

jlink --modulepath $JAVA_HOME/jmods:mlib --addmods nl.codecentric.test,nl.codecentric.fastsocket --output linkout

There might be an option in the future for JLink to bind explicitly; https://twitter.com/mreinhold/status/665122968851382273, which might at least make it more explicit.

Hope you liked this blog and would love to hear your comments!

Comment

Your email address will not be published. Required fields are marked *