Search code examples
javalog4jclassloaderslf4jjca

Changing the Priority of Java Classloaders


It's possible this has already been asked, but if so I've been unable to find it.

The Ask

Is there a way in Java 1.7/1.8, short of implementing a custom classloader, to make the application classpath higher-priority (loaded earlier) than the extension classpath?

The Issue

We have a platform with multiple apps using Apache's log4j library. We also have a custom JCA security provider installed in [jre]/lib/ext which also uses log4j. In order to do so, log4j has to be installed along with the provider in the ext directory.

One of the apps on the platform (Apache's activemq) relies on an older version of log4j/slf4j than the provider's. And since the provider's log4j jars are in [jre]/lib/ext, they override activemq's, causing a NoSuchMethodError to occur:

java.lang.NoSuchMethodError: org.slf4j.spi.LocationAwareLogger.log(Lorg/slf4j/Marker;Ljava/lang/String;ILjava/lang/String;Ljava/lang/Throwable;)V

So is there a way to make the jars in the application classpath supersede the extension directory?

Reproduction

You can reproduce this issue by installing these jars into [jre]/lib/ext:

  • log4j-1.2.17.jar
  • slf4j-api-1.7.2.jar
  • slf4j-log4j12-1.7.2.jar

And by installing these jars in the application classpath:

  • log4j-1.2.14.jar
  • slf4j-api-1.5.11.jar
  • slf4j-log4j12-1.5.11.jar

And by running this code:

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import org.slf4j.LoggerFactory;
import org.slf4j.spi.LocationAwareLogger;

public class LogApp {

    public final static LocationAwareLogger logger =
            (LocationAwareLogger) LoggerFactory.getLogger(LogApp.class);

    public static void main(String[] args) throws NoSuchAlgorithmException {
        // Make sure custom security provider has been initialized.
        SecureRandom rand = SecureRandom.getInstanceStrong();
        rand.doubles();

        //ClassLoader cl = ClassLoader.getSystemClassLoader();

        LogApp.logger.error("error: {}: {}", "string", new Exception());
        LogApp.logger.log(null, LogApp.class.getCanonicalName(),
                logger.ERROR_INT, "some message", new Exception());
    }

}

Which should generate this error:

Exception in thread "main" java.lang.NoSuchMethodError: org.slf4j.spi.LocationAwareLogger.log(Lorg/slf4j/Marker;Ljava/lang/String;ILjava/lang/String;Ljava/lang/Throwable;)V
    at LogApp.main(LogApp.java:23)

Answer

Run Java with this parameter:

-Xbootclasspath/p:[path_to_app_libs]/log4j-1.2.14.jar:[path_to_app_libs]/slf4j-api-1.5.11.jar:[path_to_app_libs]/slf4j-log4j12-1.5.11.jar

Solution

  • If you have control how your java is run, you can use -Xbootclasspath/a:path/to/your.jar to force loading specific jar ahead of extensions.

    Upgrading log4j in lib/ext might be easier solution. Hopefully, whatever requires log4j in lib/ext will survive newer version, otherwise, you will have issues in any case, as bootclasspath will take precedence and force your lib/ext code to use new version.

    In any case, having libraries with specific versions of log4j is bad pratice. Anything but final application, should probably use slf4j without specific binding, or possibly JUL.