Search code examples
javabyte-buddy

What is causing duplicate class definition for java.lang.ClassLoader$ByteBuddyAccessor$V1 with INJECTION strategy?


When updating to bytebuddy 1.12.22 (from 1.10.14) we have started occasionally seeing exception indicating that java.lang.ClassLoader$ByteBuddyAccessor$V1 is (already) in module java.base of loader 'bootstrap'. We have bytebuddy (shaded into one of our jars) included in our wars. We only occasionally see this issue when we have multiple wars running in the same tomcat instance. For tomcat instances with only 1 war or simple single classpath applications, there are never issues.

We are calling DynamicType.Unloaded.load with a classloader from class in same war and ClassLoadingStrategy.Default.INJECTION strategy.

https://javadoc.io/static/net.bytebuddy/byte-buddy/1.14.4/net/bytebuddy/dynamic/DynamicType.Unloaded.html#load-S-net.bytebuddy.dynamic.loading.ClassLoadingStrategy-

This is failing with message:

java.lang.UnsupportedOperationException: Cannot define class using reflection: java.lang.LinkageError: loader 'bootstrap' attempted duplicate class definition for java.lang.ClassLoader$ByteBuddyAccessor$V1. (java.lang.ClassLoader$ByteBuddyAccessor$V1 is in module java.base of loader 'bootstrap')

It looks like there was a change which moved from generating an accessor class from having a random name to always using "V1". https://github.com/raphw/byte-buddy/commit/201c6b69480b5b9ee35b5cdc9e35fb1bab2005dc#diff-f2e06c83c21b2a52e4ebf4392e271c68d13cdc2e5fba055773f1ffd81ce18776

github change

Is this the expected behavior? Is it not allowed to have byte buddy in multiple wars in the same tomcat instance, or at least not allowed if using INJECTION strategy?


Solution

  • The corresponding code looks checks if the class is already loaded:

    synchronized (classLoader == null
      ? BOOTSTRAP_LOADER_LOCK
      : classLoader) {
        for (Map.Entry<? extends String, byte[]> entry : types.entrySet()) {
          try {
            result.put(entry.getKey(), Class.forName(entry.getKey(), false, classLoader));
          } catch (ClassNotFoundException ignored) {
            result.put(entry.getKey(), dispatcher.defineClass(classLoader, entry.getKey(), entry.getValue(), protectionDomain));
          }
        }
      }
    

    This should prevent the error you are experiencing. Could you check why this happens? The change was made to support GraalVM which struggles with randomized names.

    I could not reproduce this, at least: https://github.com/raphw/byte-buddy/commit/fa4b2ac09db71352c248900a633a4adc0d69124b