NoClassDefFoundError at Runtime with Gradle

 ̄綄美尐妖づ 提交于 2019-11-27 22:59:34

it's a good question, which I came across just now while researching examples of the many ways Java developers can end up with class path fun :-)

I started with a minimal version of your build.gradle (including only what's directly relevant), specifically:

plugins {
    id 'java'
}
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

jar {
    manifest {
        attributes 'Main-Class': 'com.oliverlockwood.Main'
    }
}

dependencies {
    compile 'org.apache.httpcomponents:httpclient:4.5.1'
}

My 'Main' class, in this context, uses your code example, i.e.:

package com.oliverlockwood;

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class Main {
    public static void main(String[] args) {
        CloseableHttpClient client = HttpClients.createDefault();
    }
}

At this stage, I can run gradle clean build followed by java -jar build/libs/33106520.jar (my project was named after this StackOverflow question) and I see this:

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/http/impl/client/HttpClients
    at com.oliverlockwood.Main.main(Main.java:8)
Caused by: java.lang.ClassNotFoundException: org.apache.http.impl.client.HttpClients
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)

This is subtly different from your error, but before we dig and reproduce that, let me emphasise something: both this error and the one you're seeing are caused at runtime when the classloader is unable to find a class that it needs. There's quite a good blog post here with some more details about the difference between compile-time classpath and runtime classpaths.

If I run gradle dependencies I can see the runtime dependencies for my project:

runtime - Runtime classpath for source set 'main'.
\--- org.apache.httpcomponents:httpclient:4.5.1
     +--- org.apache.httpcomponents:httpcore:4.4.3
     +--- commons-logging:commons-logging:1.2
     \--- commons-codec:commons-codec:1.9

I added these manually one-by-one to my runtime classpath. (For the record, this isn't generally considered good practice; but for the sake of the experiment, I copied these jars to my build/libs folder and ran with java -cp build/libs/33106520.jar:build/libs/* com.oliverlockwood.Main. Interestingly enough, this wasn't able to reproduce your exact problem. To recap:

  • Without org.apache.httpcomponents:httpclient available at runtime, then we fail because the HttpClients jar is not found.
  • With org.apache.httpcomponents:httpclient:4.5.1 available at runtime, then your problem does not manifest - and I note that the class your build fails to find (org.apache.http.conn.ssl.SSLConnectionSocketFactory) is part of this same Apache library, which is very suspicious indeed.

My suspicion is then that your runtime classpath contains a different version of the Apache httpclient library. Since there's a whole lotta versions out there, I'm not going to test every single combination, so I will instead leave you with the following advice.

  1. If you want to fully understand the root cause of your issue, then identify exactly which jars (including their versions) are present in your error-case runtime classpath, including any jars that are packaged inside yours if you're creating a fat jar (more on this in point 3). It'd be great if you shared these details here; root cause analysis usually helps everyone to understand better :-)
  2. Where possible, avoid using dependencies in the manner of compile fileTree(dir: 'lib', include: ['*.jar']). Managed dependencies based on a repository such as Maven or JCenter are much easier to work with consistently than dependencies in a random directory. If these are internal libraries that you don't want to publish to an open-source artifact repository, then it may be worth setting up a local Nexus instance or similar.
  3. Consider producing a "fat jar" instead of a "thin jar" - this means that all runtime dependencies are packaged in the jar that you build. There's a good Shadow plugin for Gradle that I'd recommend - with this in place in my build.gradle, and running gradle clean shadow, I was able to run java -jar just fine without needing to manually add anything to my classpath.
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!