Search code examples
javajava-platform-module-systemserviceloader

JPMS ServiceLoader does not work for me as expected


JPMS ServiceLoader does not work for me as expected.

I am trying to provide a desktop program as an executable jar with a default service, that can be overloaded by the individual user. A user provides their own service class and give their name as an argument on the commandline.

The service:

package eu.ngong.myService;

public interface MyService {
    public String name();
    public void doSomething();
}

The program together with the default service (the first lines in the if and the for are inserted for logging):

package eu.ngong.myService;

import java.util.ServiceLoader;

public class ServiceUser implements MyService {
    private static MyService myService = new ServiceUser();

    public static void main(String[] args) {
        if (args.length > 0) {
            System.out.println("trying to load " + args[0] + " envirionment.");
            ServiceLoader<MyService> myServices = ServiceLoader.load(MyService.class);
            for (MyService ms : myServices) {
                System.out.println(ms.name());
                if (ms.name().equalsIgnoreCase(args[0])) {
                    myService = ms;
                }
            }
        }
        myService.doSomething();
    }

    @Override
    public void doSomething() {
        System.out.println("The default service is acting.");
    }

    @Override
    public String name() {
        return "Default";
    }
}

Both collected in myService.jar with the main class of ServiceUser hosting the module-info.java

module MyService {
    exports eu.ngong.myService;
    provides eu.ngong.myService.MyService with eu.ngong.myService.ServiceUser;
}

An individual jar at a user may be

package eu.ngong.user1;
import eu.ngong.myService.MyService;
public class User1 implements MyService {

    @Override
    public String name() {
        return "User1";
    }

    @Override
    public void doSomething() {
        System.out.println("User1 is acting.");
    }

}

with the module-info.java

module User1 {
    requires MyService;
    provides eu.ngong.myService.MyService with eu.ngong.user1.User1;
}

However, running the program with

java -p ..\user1\user1.jar;myService.jar -jar myService.jar User1

leads only to the unexpected output

trying to load User1 environment.
The default service is acting.

While I expected with logging

trying to load User1 environment.
Default
User1
User1 is acting.

What did I miss?


Solution

    1. You need to declare

      uses eu.ngong.myService.MyService;
      

    in the module where you want to execute the service loading code.

    1. Try ensuring your packaging added META-INF/services for the correct resolution of modules via jar files if processed as automatic.

    Since this wouldn't fit in the comments, hence sharing the findings of a similar example here. I could notice upon execution via IntelliJ that the code just worked with the uses declaration and the classpath of user-service module.

    Further I tried to use a similar command line:

    java --show-module-resolution -p base-service/target/classes:user-service/target/classes --add-modules base.service,user.service -m base.service/base.service.ServiceUser user1
    root base.service file://.../base-service/target/classes/
    root user.service file://.../user-service/target/classes/
    user.service requires base.service file://.../base-service/target/classes/
    base.service binds user.service file://.../user-service/target/classes/
    ...
    trying to load user1 environment.
    Services found : 2
    User1
    Default
    User1 is acting.
    

    and added the --show-module-resolution to seek why providing the jar's on the module path didn't work. The output was as follows and had a difference where the service module was not able to bind the user module.

    java --show-module-resolution -p base-service/target/base-service-1.0-SNAPSHOT.jar:user-service/target/user-service-1.0-SNAPSHOT.jar --add-modules base.service,user.service -m base.service/base.service.ServiceUser user1
    root base.service file://...base-service/target/base-service-1.0-SNAPSHOT.jar automatic
    root user.service file://...user-service/target/user-service-1.0-SNAPSHOT.jar
    user.service requires base.service file://...base-service/target/base-service-1.0-SNAPSHOT.jar automatic
    .....
    trying to load user1 environment.
    Services found : 1
    User1
    User1 is acting.
    

    This might be the cause for unexpected output in the way you are executing as well. The slight difference being the Default implementation is not resolved in the latter for me.