Reputation: 629
I have a maven project where in I included a jar that I created using gradle as a pom dependency. In that included Jar's code, I am referencing log4j logmanager. When I try to access a method in the external jar, it throws java.lang.NoClassDefFoundError on logmanager that the class inside the exernal jar is referring to.
build.gradle for exernal jar is:
plugins {
id 'java'
}
group 'com.somecompany.somethingelse'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.0'
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.0'
}
I build the jar using gradle clean assemble
I install this jar locally in to .m2 using mvn install:install-file and then have a dependency of it in the pom for the consuming app.
I am not really sure what is going on here.
package com.company.something;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public abstract class MyClass{
private static Logger logger = LogManager.getLogger();
public static String myMethod(String someInput){
logger.info("entered myMethod");
......some code goes here.....
}
}
import com.company.something.MyClass;
public class consumingClass{
public String consumingMethod(){
MyClass.myMethod("someinput");
return "something";
}
}
Upvotes: 1
Views: 1103
Reputation: 7598
When you use mvn install:install-file
, you are installing the jar file and creating a default pom for it using plain Maven. If you don't do anything else, the pom will not contain any of the transitive dependencies. After all, Maven just sees a jar file and knows nothing about the surrounding Gradle scripts. This is why it fails at runtime as the Log4J dependencies are missing from the library ("external") pom.
What you should do instead is to use the Maven Publish Plugin for Gradle to create a proper pom for your library. Do this by adding:
plugins {
id 'maven-publish'
}
publishing {
publications {
myLibrary(MavenPublication) {
from components.java
}
}
}
You can then upload the jar file to your local .m2 repository with a full pom using gradle publishToMavenLocal
.
Also, ysakhno's answer is correct in the part about not needing log4j-core
on your compilation classpath - it's simply bad practice. Instead, you should either remove it and make the consuming project add it as an explicit dependency, or change the configuration from implementation
to runtimeOnly
. Both approaches are fine, depending on how tight you want to couple Log4j with your library.
I also think it is perfectly fine to use the Log4J2 API in a library, even if it could be consumed in projects using many different logging implementations. After all, it is just as easy to bind the Log4J2 API to SLF4J, as it is the other way around. And both are popular and very good choices.
Upvotes: 1
Reputation: 863
In general, the reason why logging libraries have 2 JARs (like in the sample you're presenting), is to let libraries compile against just the API JAR of the library, and then work (execute) at runtime with the actual implementation of the logging library (that would be log4j-core
in your case) present somewhere on the classpath of the consuming application.
With the above in mind, you have to separate the dependencies between the library and the app, i.e. in the library's build.gradle
you should have this:
dependencies {
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.0'
// Note: you do not need the 'actual' implementation of Log4j in your library
// at all! It should compile very well with just the API, you'll then have
// to put an 'implementation' dependency on log4j-core in your consuming
// application's build.gradle (or pom.xml for that matter)
}
And then in your application's pom.xml you will have to put this:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.13.0</version>
</dependency>
If you do want to leave everything as is (strongly not recommended) and have the dependencies from the library to be included in the consuming application, then use the api
dependency configuration instead of the implementation
one. Here is a good StackOverflow answer explaining the difference between the two.
On a side-note, I would suggest making your library dependent not on Log4j's API, but on Simple Logging Facade for Java one, because then the consuming application could choose which of the logging libraries implementations to use. As specified in SLF4J's FAQ:
[...] libraries and other embedded components should consider SLF4J for their logging needs because libraries cannot afford to impose their choice of logging framework on the end-user.
Note: Whatever you choose to do, do not use compile
dependency configuration in Gradle build script, because it has been deprecated some time ago, and might not work with future versions of Gradle. The configuration roughly equivalent to compile
is api
.
Upvotes: 0
Reputation: 413
You may want to add this
compile group: 'org.apache.logging.log4j', name: 'log4j-1.2-api', version: '2.2'
too
Idea is that you just don't have enough dependencies to satisfy your needs. It happens often with such things as loggers
Refer: unexpected exception: java.lang.NoClassDefFoundError: org/apache/log4j/LogManager
Upvotes: 0