Search code examples
javaserviceloaderjava-18

Why should an application module not require a module that provides a service?


The ServiceLoader.java docs notes:

It is strongly recommended that the application module does not require modules which contain providers of the service.

Why is this strongly recommended, what could happen if the recommendation isn't followed?


Context: This indirectly means that modules defining a service shouldn't also export a provider of that service. I thought it would be handy to provide a default implementation of the service within the same module.


Solution

  • Why Not Require Provider Module?

    The reason is because the uses / provivdes directives and the java.util.ServiceLoader API is designed for a plugin-like architecture. You control which plugins are available by controlling which providers are on the module-path/class-path. If you requires the provider module, then you can no longer omit it because the application would fail to launch due to missing dependencies.

    The Documentation

    The documentation you quote makes more sense if you look at the previous sentence as well, which provides more context.

    In addition, if the application module does not contain the service, then its module declaration must have a requires directive that specifies the module which exports the service. It is strongly recommended that the application module does not require modules which contain providers of the service.

    This is addressing the specific case demonstrated below.

    Application Module

    module app {
      requires service;
    }
    

    Service Module

    module service {
      exports com.example.service;
    
      uses com.example.service.Service;
    }
    

    Provider Module

    module provider {
      requires service;
    
      provides com.example.service.Service with
        com.example.provider.ServiceImpl;
    }
    

    The above is the recommended approach. And what the documentation is saying is that the app module should not include a requires provider directive. The reason why has already been explained.

    Also, note this does not prevent either the service module or the app module from providing a default implementation of the service interface.

    Example Module Resolution

    If you create and compile an implementation for the above modules, then you can see the module resolution at run-time via --show-module-resolution. I use --limit-modules below to control which modules are resolved to avoid having to mess around with the module-path. As you can see, since app does not requires provider, it is possible to omit provider and still have a working application.

    My service interface has a getMessage() method that simply returns a String. My main class iterates the available providers, if any, and outputs the provider's class name and "message". This output comes after, and is clearly distinct from, the module resolution output below.

    Note I load the providers from a static method in the Service interface, because the service module is where I have the uses directive for said interface.

    With Provider Module

    Command:

    java --show-module-resolution --limit-modules app,service,provider --module-path <path> --module app/com.example.app.Main
    

    Output:

    root app <module-location>
    app requires service <module-location>
    service binds provider <module-location>
    provider requires service <module-location>
    
    APPLICATION OUTPUT
    Provider: 'com.example.provider.ServiceImpl'
        message => Hello, World!
    

    Without Provider Module

    Command:

    java --show-module-resolution --limit-modules app,service --module-path <path> --module app/com.example.app.Main
    

    Output:

    root app <module-location>
    app requires service <module-location>
    
    APPLICATION OUTPUT
    There are no available providers...
    

    Previous Answer

    The previous edition of this answer had the following example to demonstrate what the documentation was saying:

    Service Module

    module service {
      requires provider;
    
      exports com.example.service;
    
      uses com.example.service.Service;
    }
    

    Provider Module

    module provider {
      requires service;
    
      provides com.example.service.Service with
        com.example.provider.ServiceImpl;
    }
    

    Not only was I incorrect regarding the purpose of the documentation, but the above is not even possible as the Java Platform Module System does not allow cyclic dependencies at compile-time.