ClassNotFoundException when using .nar dependencies
Environment:
- Jib version: 3.3.1
- Build tool: Gradle 7.4
- OS: macOS 12
Description of the issue:
When packaging a Java application with jib-gradle-plugin and running the resulting docker image the application crashes with a ClassNotFoundException.
In our particular example the error was ClassNotFoundException: com.scurrilous.circe.checksum.Crc32cIntChecksum, which is a dependency of Pulsar.
Expected behavior: The application should run fine and all dependencies should be included in the docker image.
Steps to reproduce:
I tried creating a project with as little dependencies as possible but there aren't that many .nar libraries out there.
So I used Pulsar but I don't know how to reproduce the class not found error, there must be some specific configuration for Pulsar that I am missing.
Anyways, I think we can clearly see the problem without getting to the ClassNotFoundException:
- Add pulsar as a dependency to your project:
dependencies {
implementation 'org.apache.pulsar:pulsar-client-original:2.10.2'
}
- Create the docker image with jib:
jib.from {
image = 'amazoncorretto:11'
}
jib.to {
image = "proof-of-concept:latest"
}
jib.container {
mainClass = 'org.example.Main'
}
- Generate the image and run it.
- Inside the container execute
cat /app/jib-classpath-fileto see the list of dependencies. You will not seecirce-checksum-4.14.5.narnorcpu-affinity-4.14.5.nar. - If you look at the libs folder with
ls /app/libs/ | grep naryou will see the.narfiles there.
So this means the dependencies are copied to the image, as they should, but they are not included in the classpath file and thus are "invisible" to the application, which explains the class not found.
Additional Information: I think the issue is the way jib calculates dependencies:
@Override
public List<Path> getDependencies() {
List<Path> dependencies = new ArrayList<>();
FileCollection runtimeClasspath = project.getConfigurations().getByName(configurationName);
// To be on the safe side with the order, calling "forEach" first (no filtering operations).
runtimeClasspath.forEach(
file -> {
if (file.exists()
&& file.isFile()
&& file.getName().toLowerCase(Locale.US).endsWith(".jar")) {
dependencies.add(file.toPath());
}
});
return dependencies;
}
https://github.com/GoogleContainerTools/jib/blob/master/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java#L309
This excludes anything that is not a .jar file and would explain the problem.
Workaround: If you are facing this problem, one thing you can do is override the entrypoint of the docker image like this:
jib.container {
mainClass = 'org.example.Main'
entrypoint = [
'/bin/bash',
'-c',
'java -cp $(cat /app/jib-classpath-file):$(echo /app/libs/*.nar | tr \' \' \':\') @/app/jib-main-class-file'
]
}
This will add all *.nar files from /app/libs/ to the classpath, on top of whatever the jib-classpath-file contains.
@salvalabajos Thanks for the analysis and providing a workaround to demonstrates how the jib.container.entrypoint can be leveraged in case you don't want to rely on the default entrypoint. Adding in a reference doc link as a supplement: https://github.com/GoogleContainerTools/jib/blob/afe98e7f23a0ea56b6b3bbde29600280777fe75e/jib-gradle-plugin/README.md#custom-container-entrypoint
@mpeddada1 Is this issue need fix should I pick this up ?
@Abhijeetmishr Thanks for reaching out. We are accepting contributions for this issue.
@mpeddada1 Hey Mridula, I have tried building project locally using this command ./gradlew build but I am getting some error any hints/suggestion.
Regrads
Jib does not work properly for all projects embedding nar files (ex: project embedding the Apache pulsar client) You just need to select files ending by .jar OR .nar to solve this issue...
Please see https://github.com/GoogleContainerTools/jib/blob/586b4a6f57f208bb4128aa7286741a23bf9eda7b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java#L310