3

Here you can find the the following about automatic modules:

The module system also scans META-INF/services and makes the automatic module provide the services named therein. An automatic module is assumed allowed to use all services.

However, I have the following situation. I want to use log4j2 with slf4j in JPMS. In order to do it log4j-slf4j-impl-2.11.1.jar must provide JPMS service to slf4j-api-1.8.0-beta2.jar. The developers of log4j made log4j-slf4j-impl-2.11.1.jar as automatic module and provided service via META-INF/services. However, it doesn't work, at it gives the following:

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/logging/log4j/Logger
    at org.apache.logging.log4j.slf4j@2.11.1/org.apache.logging.slf4j.SLF4JServiceProvider.initialize(SLF4JServiceProvider.java:53)
    at org.slf4j/org.slf4j.LoggerFactory.bind(LoggerFactory.java:153)
    at org.slf4j/org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:141)
    at org.slf4j/org.slf4j.LoggerFactory.getProvider(LoggerFactory.java:419)
    at org.slf4j/org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:405)
    at org.slf4j/org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:354)
    at org.slf4j/org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:380)
    at Log4j2Slf4jJdk11/com.temp.NewMain.<clinit>(NewMain.java:12)
Caused by: java.lang.ClassNotFoundException: org.apache.logging.log4j.Logger
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    ... 8 more

I decided to add module-info to log4j-slf4j-impl-2.11.1.jar and export service in JPMS manner via provides ... with... And the problem was solved - I don't get any NoClassDefFoundError. This is link to the issue.

So my questions:

  1. does JPMS support services in META-INF/services of automatic modules?
  2. if yes, then how to explain such behavior?

EDIT There are total 5 modules:

slf4j-api-1.8.0-beta2.jar  // name: org.slf4j
log4j-slf4j18-impl-2.11.1.jar // name: org.apache.logging.log4j.slf4j
log4j-core-2.11.1.jar // name: org.apache.logging.log4j.core
log4j-api-2.11.1.jar // name: org.apache.logging.log4j
log4j2-slf4j-jdk11-1.0-SNAPSHOT.jar // name: Log4j2Slf4jJdk11

VARIANT 1 If I run --show-module-resolution when log4j-slf4j18-impl-2.11.1.jar has META-INF/services I get the following piece of output (I replaced full path with ...):

...
root Log4j2Slf4jJdk11 file:.../log4j2-slf4j-jdk11-1.0-SNAPSHOT.jar
Log4j2Slf4jJdk11 requires org.slf4j file:.../slf4j-api-1.8.0-beta2.jar
jdk.compiler binds org.apache.logging.log4j.core file:.../log4j-core-2.11.1.jar automatic
org.slf4j binds org.apache.logging.log4j.slf4j file:.../log4j-slf4j18-impl-2.11.1.jar automatic

VARIANT 2 If I run --show-module-resolution when log4j-slf4j18-impl-2.11.1.jar has module-info I get the following piece of output:

...
root Log4j2Slf4jJdk11 file:.../log4j2-slf4j-jdk11-1.0-SNAPSHOT.jar
Log4j2Slf4jJdk11 requires org.slf4j file:.../slf4j-api-1.8.0-beta2.jar
jdk.compiler binds org.apache.logging.log4j.core file:.../log4j-core-2.11.1.jar automatic
org.slf4j binds org.apache.logging.log4j.slf4j file:.../log4j-slf4j18-impl-2.11.1.jar
org.apache.logging.log4j.slf4j requires org.slf4j file:.../slf4j-api-1.8.0-beta2.jar
org.apache.logging.log4j.slf4j requires org.apache.logging.log4j.core file:.../log4j-core-2.11.1.jar automatic
org.apache.logging.log4j.slf4j requires org.apache.logging.log4j file:.../log4j-api-2.11.1.jar
org.apache.logging.log4j binds org.apache.logging.log4j.core file:.../log4j-core-2.11.1.jar automatic

In VARIANT 1 the service from org.apache.logging.log4j.slf4j can not load class (org.apache.logging.log4j.Logger) from org.apache.logging.log4j.core. In VARIANT 2 the service from org.apache.logging.log4j.slf4j loads all classes from org.apache.logging.log4j.core and everything is ok. We see in the ouput of VARIANT 2 that there is a line

org.apache.logging.log4j.slf4j requires org.apache.logging.log4j.core

and there is no such line in VARIANT 1. Is the problem in this? But if two modules are automatic can't they be resolved automatically?

Pavel_K
  • 10,748
  • 13
  • 73
  • 186
  • Well, it looks like slf4j does not use [ServiceLoader](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html). You might need to add a read edge at runtime. – Johannes Kuhn Nov 05 '18 at 10:03
  • 2
    Running with `--show-module-resolution` is a good way to see which modules are resolving because of service binding. – Alan Bateman Nov 05 '18 at 13:41
  • JohannesKuhn SLF4J 1.8 does use the ServiceLoader. @AlanBateman Thanks - When I use --list-modules to test the program Pavel provided (see the linked jira issue) the output includes the log4j-api jar. When I use --show-modules the log4j-api jar is not included. I am specifying --modules-path=lib and all the jars are in that directory. Why is it not finding the log4j-api module? I should note that no "real" modules declare they need log4j-api, but the log4j-slf4j18-impl automatic module needs it. – rgoers Nov 05 '18 at 14:32
  • @Pavel_K In VARIANT1 it is not finding org.apache.logging.log4j.Logger from Log4j API. Notice that your output does not show org.apache.logging.log4j being included, only org.apache.logging.log4j.core. If you include --add-modules=org.apache.logging.log4j then your test application works. This means that automatic modules do not cause the explicit modules they may reference to be loaded. – rgoers Nov 05 '18 at 16:28
  • @rgoers Yes, I agree with you. The problem is in loading modules. So, we can close that issue. – Pavel_K Nov 05 '18 at 16:34

1 Answers1

3

SLF4J 1.8 requires an implementation of org.slf4j.spi.SLF4JServiceProvider as an exposed service. It is finding that in the log4j-slf4j18-impl jar. However, the Log4j SLF4J bridge requires the Log4J API (module org.apache.logging.log4j). Even though that is an explicit Java module, because it is only referenced from an automatic module it is not being loaded, resulting in the ClassNotFoundException.

The simple solution to this is to include --addmodules=org.apache.logging.log4j on the command line when starting the application.

rgoers
  • 8,696
  • 1
  • 22
  • 24
  • 1
    I have done an experiment where I managed to remove the need for --add-module by changing the ordering of the modules on the jlink --module-path. Might be worth an experiment. – Emma Atkinson Nov 10 '18 at 15:13