Reputation: 31
Our ByteBuddy implementation is working when running locally via IntelliJ, but once packaged and deployed in a Docker Container it breaks throwing a ClassNotFoundException
We have the following method that Redefines an already existing class. Just adding some annotations to it at runtime basically, I did not use any installation agent (ByteBuddyAgent):
private DynamicType.Loaded<? extends MyClassToRedefine> getLoadedTypeOfResource() {
if (myClassToRedefineImplLoadedType == null) {
try (var unloadedResource = new ByteBuddy()
.subclass(MyClassToRedefine.class)
.annotateType(newAnnotation1)
.annotateType(newAnnotation2)
.annotateType(newAnnotation3)
.make()) {
myClassToRedefineImplLoadedType = unloadedResource.load(ClassLoader.getSystemClassLoader());
}
}
return myClassToRedefineImplLoadedType;
}
On my run local (IntelliJ, SpringBoot, JDK21, Maven) my application is starting correctly, the class is redefined accordingly.
But, when I try to package the application into a jar file and run it inside a docker container with the following dockerfile:
FROM openjdk:21-jdk
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
I'm getting a ClassNotFoundException error:
Caused by: java.lang.ClassNotFoundException: com.abc.def.MyClassToRedefine
at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.findClass(ByteArrayClassLoader.java:404)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:593)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
... 73 common frames omitted
I've tried putting some log messages before this line is executed:
myClassToRedefineImplLoadedType = unloadedResource.load(ClassLoader.getSystemClassLoader());
and it seems the class should be defined, but once the line is executed, it throws the error.
I tried implementing the suggested method here: https://www.baeldung.com/byte-buddy (item no. 6) which is to use a ByteBuddyAgent and a Classloading Strategy ClassReloadingStrategy.fromInstalledAgent() and it now WORKS for BOTH local and DOCKER. Here is the code:
private DynamicType.Loaded<? extends MyClassToRedefine> getLoadedTypeOfResource() {
ByteBuddyAgent.install();
if (provisionedCustomResourceImplLoadedType == null) {
try (var unloadedResource = new ByteBuddy()
.subclass(MyClassToRedefine.class)
.annotateType(newAnnotation1)
.annotateType(newAnnotation2)
.annotateType(newAnnotation3)
.make()) {
myClassToRedefineImplLoadedType = unloadedResource.load(MyClassToRedefine.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
}
}
return myClassToRedefineImplLoadedType;
}
Can someone explain why the first approach is working at local intellij run but not in docker container, but the 2nd approach works for both?
What's the difference between the ClassLoader.getSystemClassLoader() vs MyClassToRedefine.class.getClassLoader() to make them have a different behavior in a local and docker setup?
Upvotes: 3
Views: 118
Reputation: 44032
You could enable logging for class loading, and see if the class in question is already loaded on Docker.
I assume that you are running a different JDK locally, and factors such as class verification might differ and affect class loading.
Generally speaking, the approach of redefining a class without agent only works reliably, if the classes are to be used in a different JVM. For your case, I would recommend the agent.
Note that dynamic attach is restricted from Java 22. Therefore, I would recommend you to add an agent to the command line at least for your Docker based setup. It will also improve startup performance.
Upvotes: 2