This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-20141 in repository https://gitbox.apache.org/repos/asf/camel.git
commit a92e013f8393156a6aa134090d49a0a9624be644 Author: Claus Ibsen <[email protected]> AuthorDate: Tue Jul 14 11:35:24 2026 +0200 CAMEL-20141: camel-joor - Recompile all Java files together during hot-reload During dev mode hot-reload, when a .java file changes only that file was recompiled in a new ByteArrayClassLoader. Other Java files (e.g. a builder class) stayed loaded from the original classloader. This caused class identity mismatch when the builder created objects of the changed class, making convertTo() return null and failing with NoSuchBeanException. Fix: JavaRoutesBuilderLoader now tracks all previously compiled Java resources and includes them in any recompilation, ensuring all classes share the same ByteArrayClassLoader and class identity is consistent. Also fix the classloader parent chain so ByteArrayClassLoader delegates through CamelJoorClassLoader to JavaJoorClassLoader for proper class resolution. Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../camel/language/joor/ByteArrayClassLoader.java | 5 +++++ .../camel/language/joor/CamelJoorClassLoader.java | 5 +++++ .../apache/camel/language/joor/MultiCompile.java | 2 +- .../dsl/java/joor/JavaRoutesBuilderLoader.java | 21 +++++++++++++++++++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/components/camel-joor/src/main/java/org/apache/camel/language/joor/ByteArrayClassLoader.java b/components/camel-joor/src/main/java/org/apache/camel/language/joor/ByteArrayClassLoader.java index 3e4153423929..f819e400548f 100644 --- a/components/camel-joor/src/main/java/org/apache/camel/language/joor/ByteArrayClassLoader.java +++ b/components/camel-joor/src/main/java/org/apache/camel/language/joor/ByteArrayClassLoader.java @@ -30,6 +30,11 @@ final class ByteArrayClassLoader extends ClassLoader { this.classes = classes; } + public ByteArrayClassLoader(ClassLoader parent, Map<String, byte[]> classes) { + super(parent); + this.classes = classes; + } + @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] bytes = classes.get(name); diff --git a/components/camel-joor/src/main/java/org/apache/camel/language/joor/CamelJoorClassLoader.java b/components/camel-joor/src/main/java/org/apache/camel/language/joor/CamelJoorClassLoader.java index 6d478d4f3ccc..07ce710f962a 100644 --- a/components/camel-joor/src/main/java/org/apache/camel/language/joor/CamelJoorClassLoader.java +++ b/components/camel-joor/src/main/java/org/apache/camel/language/joor/CamelJoorClassLoader.java @@ -73,6 +73,11 @@ public class CamelJoorClassLoader extends URLClassLoader { return doLoadClass(name); } + @Override + protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { + return doLoadClass(name); + } + public Class<?> doLoadClass(String name) throws ClassNotFoundException { // first try JavaJoorClassLoader ClassLoader joorClassLoader = camelContext.getClassResolver().getClassLoader("JavaJoorClassLoader"); diff --git a/components/camel-joor/src/main/java/org/apache/camel/language/joor/MultiCompile.java b/components/camel-joor/src/main/java/org/apache/camel/language/joor/MultiCompile.java index 66905b1543d1..55f1ff0d6bf7 100644 --- a/components/camel-joor/src/main/java/org/apache/camel/language/joor/MultiCompile.java +++ b/components/camel-joor/src/main/java/org/apache/camel/language/joor/MultiCompile.java @@ -183,7 +183,7 @@ public final class MultiCompile { // We need a private-access lookup from the class in that stack frame in order to get // private-access to any local interfaces at that location. int index = 2; - ByteArrayClassLoader c = new ByteArrayClassLoader(fileManager.classes()); + ByteArrayClassLoader c = new ByteArrayClassLoader(cl, fileManager.classes()); for (CharSequenceJavaFileObject f : files) { String className = f.getClassName(); diff --git a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java index d45ed2d6a406..72765491dbb5 100644 --- a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java +++ b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java @@ -158,7 +158,7 @@ public class JavaRoutesBuilderLoader extends ExtendedRouteBuilderLoaderSupport { // inject context and resource CamelContextAware.trySetCamelContext(obj, getCamelContext()); - ResourceAware.trySetResource(obj, nameToResource.remove(className)); + ResourceAware.trySetResource(obj, nameToResource.get(className)); } } catch (Exception e) { throw new RuntimeCamelException("Cannot create instance of class: " + className, e); @@ -212,6 +212,22 @@ public class JavaRoutesBuilderLoader extends ExtendedRouteBuilderLoaderSupport { } } + // During hot-reload only the changed file is passed in, but all Java files + // must be recompiled together so class identity is consistent across classloaders + for (Map.Entry<String, Resource> entry : nameToResource.entrySet()) { + if (!unit.getInput().containsKey(entry.getKey())) { + try (InputStream is = resourceInputStream(entry.getValue())) { + if (is != null) { + String content = IOHelper.loadText(is); + unit.addClass(entry.getKey(), content); + classLoader.removeClass(entry.getKey()); + } + } catch (Exception e) { + LOG.debug("Could not read previously compiled resource: {}", entry.getValue(), e); + } + } + } + // include classloader from Camel, so we can load any already compiled and loaded classes ClassLoader parent = resolveParentClassLoader(); if (parent instanceof URLClassLoader ucl) { @@ -227,7 +243,8 @@ public class JavaRoutesBuilderLoader extends ExtendedRouteBuilderLoaderSupport { // remember the last loaded resource-set if route reloading is enabled if (getCamelContext().hasService(RouteWatcherReloadStrategy.class) != null) { - getCamelContext().getRegistry().bind(RouteWatcherReloadStrategy.RELOAD_RESOURCES, nameToResource.values()); + getCamelContext().getRegistry().bind(RouteWatcherReloadStrategy.RELOAD_RESOURCES, + new ArrayList<>(nameToResource.values())); } for (String className : result.getClassNames()) {
