/ SERVICELOADER, MODULE

Migrating the ServiceLoader to the Java 9 module system

A long long (long?) time ago, I wrote a post about the ServiceLoader. In short, the Service Loader allows to separate an API and its implementations in different JARs. The client code depends on the API only, while at runtime, the implementation(s) that is (are) on the classpath will be used. This is great way to decouple the client code from the implementing one.

For example, the ServiceLoader is used by SLF4J: one adds the slf4j-api on the classpath at compilation time, while any single implementation (e.g. slf4j-simple or logback) can be set on the classpath at runtime. This is a poster child of the service loader’s usage, cleanly separating between the contract and its implementation(s).

A sample project

To illustrate this, let’s implement out own logging project:

  1. A logging API
    LogService.java
    public interface LogService {
        void log(String message);
    }
  2. A single implementation that writes on stdout
    LogStdOut.java
    public class LogStdOut implements LogService {
        @Override
        public void log(String message) {
            System.out.println(message);
        }
    }
  3. A client that calls the API, and makes use of the service loader mechanism
    Client.java
    public class Client {
    
        public static void main(String[] args) {
            ServiceLoader<LogService> loader = ServiceLoader.load(LogService.class);
            for (LogService service : loader) {
                service.log("Log written by " + service.getClass());
            }
        }
    }

The magic happens because the client contains a service loader configuration file fulfilling several constraints:

  1. It’s located in /META-INF/services
  2. Its name is the fully-qualified name of the interface
  3. It contains the fully-qualified class name of the implementing class:
    /META-INF/services/ch.frankel.blog.serviceloader.log.LogService
    ch.frankel.blog.serviceloader.log.stdout.LogStdOut

Migrating to the Java Platform Module System

As I’ve written before, migrating a non-trivial application to the Java Platform Module System is not always possible. I don’t think a lot has changed since that time. However, the concept behind the module system itself is quite straightforward.

In regard to our sample project, the following steps are required.

Modularize the API

In order for other modules - implementation(s) and client - to use the API, the package containing the LogService interface needs to be exported.

module-info.java
module log.api {
    exports ch.frankel.blog.serviceloader.log;
}
Modularize the client

The client sits at the boundary of the module dependency tree. It just requires the API module.

module-info.java
module log.client {
    requires log.api;
}
Modularize the implementation

The implementation needs the API. It also should export the package containing the implementation, so it can be used in other modules.

However, this is not enough. Java 9 replaces how the Service Loader works, from the META-INF/services folder to a module-specific implementation.

For that, the module-info syntax offers two keywords: provides to reference the interface and with to specify the implementation:

module-info.java
import ch.frankel.blog.serviceloader.log.LogService;
import ch.frankel.blog.serviceloader.log.stdout.LogStdOut;

module log.stdout {
    requires log.api;
    exports ch.frankel.blog.serviceloader.log.stdout;
    provides LogService
        with LogStdOut;
}

Interestingly enough, only the configuration changes: the Service Loader code itself in the client doesn’t change.

Conclusion

Jigsaw makes it possible to continue reaping the benefit of the Service Loader. While the configuration changes - from the META-INF/services folder to the compiled module-info, the API stays the same. There might be some hardships regarding the migration to the module system, but the Service Loader shouldn’t be one of them.

The complete source code for this post can be found on Github.
Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Hazelcast. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More