GoogleContainerTools / GoogleContainerTools/jib
ClassNotFoundException when using .nar dependencies
- Dominant language
- Java
- Stars
- 14.5k
- Forks
- 1.5k
- PR merge metrics
- No merged PRs in 30d
Description
**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](https://pulsar.apache.org/).
**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:
1. Add pulsar as a dependency to your project:
```gradle
dependencies {
implementation 'org.apache.pulsar:pulsar-client-original:2.10.2'
}
```
2. Create the docker image with jib:
```gradle
jib.from {
image = 'amazoncorretto:11'
}
jib.to {
image = "proof-of-concept:latest"
}
jib.container {
mainClass = 'org.example.Main'
}
```
3. Generate the image and run it.
4. Inside the container execute `cat /app/jib-classpath-file` to see the list of dependencies. You will not see `circe-checksum-4.14.5.nar` nor `cpu-affinity-4.14.5.nar`.
5. If you look at the libs folder with `ls /app/libs/ | grep nar` you will see the `.nar` files 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:
```java
@Override
public List getDependencies() {
List 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:
```gradle
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.
Contributor guide
Assessment
This issue has not been assessed yet.