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()) {

Reply via email to