This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch fix/CAMEL-24879
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 5757a196c2b5dda1866779a2ee40aec052a84c6f
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Sep 21 18:59:29 2026 +0200

    CAMEL-24879: camel-groovy - Bind @BindToRegistry and @Converter classes 
from Groovy sources in every runtime
    
    The Camel-annotation compile post-processors move from camel-kamelet-main to
    camel-support (org.apache.camel.support.compile). 
DefaultGroovyScriptCompiler
    uses the processors in the registry when there are any (camel-jbang), and
    otherwise these built-in ones, so a .groovy file with @BindToRegistry or
    @Converter also works after camel export, in camel-main, Spring Boot and
    Quarkus. camel-main is not changed.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/catalog/docs/groovy-language.adoc |  12 +-
 .../apache/camel/catalog/docs/simple-advanced.adoc |   7 +-
 .../src/main/docs/groovy-language.adoc             |  12 +-
 .../groovy/DefaultGroovyScriptCompiler.java        |  28 +++--
 .../GroovyDefaultCompilePostProcessorTest.java     |  87 +++++++++++++++
 .../camel-groovy-default/MaskEmailFunction.groovy  |  34 ++++++
 .../camel-groovy-default/OrderConverter.groovy     |  27 +++++
 .../modules/languages/pages/simple-advanced.adoc   |   7 +-
 .../BindToRegistryCompilePostProcessor.java        | 105 ++++++++++++++++++
 .../compile/EventNotifierCompilePostProcessor.java |  53 +++++++++
 .../compile/TypeConverterCompilePostProcessor.java |  50 +++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |  17 ++-
 .../modules/ROOT/pages/camel-jbang-beans.adoc      |   7 +-
 .../injection/AnnotationDependencyInjection.java   | 123 +--------------------
 14 files changed, 418 insertions(+), 151 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/groovy-language.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/groovy-language.adoc
index 6806dcf3ad4a..9d51d0d55a13 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/groovy-language.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/groovy-language.adoc
@@ -192,11 +192,13 @@ with Camel features that would support this such as in 
the route DSL and elsewhe
 
 However, there may be some features in Camel where this may not work (yet).
 
-A compiled class with class-level annotations is handed to the registered 
`org.apache.camel.spi.CompilePostProcessor`s
-with a new instance, as the Java DSL does for `.java` sources. With Camel CLI 
(`camel run`) that binds a
-`@BindToRegistry` class in the Registry and registers a `@Converter` class as 
type converters (the Spring
-and Quarkus annotations are supported as well), so a Groovy source can provide 
a bean such as a custom
-xref:languages:simple-advanced.adoc[simple function]. Plain classes without 
annotations are not instantiated.
+A compiled class with class-level annotations is handed to the 
`org.apache.camel.spi.CompilePostProcessor`s with a
+new instance, as the Java DSL does for `.java` sources. The processors in the 
Registry are used when there are any
+(Camel CLI registers processors that also handle the Spring and Quarkus 
annotations); otherwise the built-in
+processors for the Camel annotations are used. So in every runtime a 
`@BindToRegistry` class is bound in the
+Registry, a `@Converter` class is registered as type converters, and an 
`EventNotifier` class is added, and a Groovy
+source can provide a bean such as a custom 
xref:languages:simple-advanced.adoc[simple function]. Plain classes
+without annotations are not instantiated.
 
 IMPORTANT: This feature is only intended to include smaller groovy sources as 
small functions, DTOs
 that makes it easier to use together with Camel for low-code integrations. It 
is not
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
index 70cc13c280e0..ad5014dfab36 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
@@ -1056,9 +1056,10 @@ In dev mode (`--dev`) an edit of the function, in the 
`.groovy` file or in the Y
 message: the bean is created again on reload and the simple expression 
resolves the function again per evaluation
 when the `dev` profile is active.
 
-NOTE: The `@BindToRegistry` binding of a `.groovy` file is done by the Camel 
CLI. In a project created with
-`camel export` the file is compiled, but the class is not bound as a bean, so 
the function is unknown. For a project
-that is exported, use the inline YAML bean shown above, which works in every 
runtime, or write the function in Java.
+NOTE: The `.groovy` file keeps working in a project created with `camel 
export`: the Groovy compiler in camel-groovy
+binds a `@BindToRegistry` class itself, so the function is available to Camel 
in camel-main, Spring Boot and Quarkus
+alike. The bean lives in Camel's registry, not in the Spring or CDI container, 
so it is not a candidate for
+`@Autowired` or `@Inject`; for that, write the class in Java.
 
 
 == JavaScript Validator
diff --git a/components/camel-groovy/src/main/docs/groovy-language.adoc 
b/components/camel-groovy/src/main/docs/groovy-language.adoc
index 6806dcf3ad4a..9d51d0d55a13 100644
--- a/components/camel-groovy/src/main/docs/groovy-language.adoc
+++ b/components/camel-groovy/src/main/docs/groovy-language.adoc
@@ -192,11 +192,13 @@ with Camel features that would support this such as in 
the route DSL and elsewhe
 
 However, there may be some features in Camel where this may not work (yet).
 
-A compiled class with class-level annotations is handed to the registered 
`org.apache.camel.spi.CompilePostProcessor`s
-with a new instance, as the Java DSL does for `.java` sources. With Camel CLI 
(`camel run`) that binds a
-`@BindToRegistry` class in the Registry and registers a `@Converter` class as 
type converters (the Spring
-and Quarkus annotations are supported as well), so a Groovy source can provide 
a bean such as a custom
-xref:languages:simple-advanced.adoc[simple function]. Plain classes without 
annotations are not instantiated.
+A compiled class with class-level annotations is handed to the 
`org.apache.camel.spi.CompilePostProcessor`s with a
+new instance, as the Java DSL does for `.java` sources. The processors in the 
Registry are used when there are any
+(Camel CLI registers processors that also handle the Spring and Quarkus 
annotations); otherwise the built-in
+processors for the Camel annotations are used. So in every runtime a 
`@BindToRegistry` class is bound in the
+Registry, a `@Converter` class is registered as type converters, and an 
`EventNotifier` class is added, and a Groovy
+source can provide a bean such as a custom 
xref:languages:simple-advanced.adoc[simple function]. Plain classes
+without annotations are not instantiated.
 
 IMPORTANT: This feature is only intended to include smaller groovy sources as 
small functions, DTOs
 that makes it easier to use together with Camel for low-code integrations. It 
is not
diff --git 
a/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/DefaultGroovyScriptCompiler.java
 
b/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/DefaultGroovyScriptCompiler.java
index 6d493c05ec1d..628742204522 100644
--- 
a/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/DefaultGroovyScriptCompiler.java
+++ 
b/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/DefaultGroovyScriptCompiler.java
@@ -47,6 +47,9 @@ import org.apache.camel.spi.Resource;
 import org.apache.camel.spi.annotations.JdkService;
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.SimpleEventNotifierSupport;
+import org.apache.camel.support.compile.BindToRegistryCompilePostProcessor;
+import org.apache.camel.support.compile.EventNotifierCompilePostProcessor;
+import org.apache.camel.support.compile.TypeConverterCompilePostProcessor;
 import org.apache.camel.support.service.ServiceSupport;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IOHelper;
@@ -68,6 +71,7 @@ public class DefaultGroovyScriptCompiler extends 
ServiceSupport
     private static final Logger LOG = 
LoggerFactory.getLogger(DefaultGroovyScriptCompiler.class);
 
     private GroovyPreCompiledClassLoader groovyPreCompiledClassLoader;
+    private List<CompilePostProcessor> defaultPostProcessors;
     private GroovyScriptClassLoader classLoader;
     private CamelContext camelContext;
     private EventNotifier notifier;
@@ -374,21 +378,29 @@ public class DefaultGroovyScriptCompiler extends 
ServiceSupport
     }
 
     /**
-     * Runs the registered {@link CompilePostProcessor}s on a compiled class, 
as the Java DSL loader does for
-     * {@code .java} sources, so annotations such as {@link BindToRegistry} 
and {@link org.apache.camel.Converter} (and
-     * the Spring and Quarkus equivalents camel-jbang registers) work in 
Groovy sources as well. On a recompile (live
-     * reload) the bean is created and bound again, replacing the previous one.
+     * Runs the {@link CompilePostProcessor}s on a compiled class, as the Java 
DSL loader does for {@code .java}
+     * sources, so annotations such as {@link BindToRegistry} and {@link 
org.apache.camel.Converter} work in Groovy
+     * sources as well. The processors in the registry are used when there are 
any (camel-jbang registers processors
+     * that also handle the Spring and Quarkus annotations); otherwise the 
built-in processors for the Camel annotations
+     * are used, so a Groovy source works the same in every runtime. On a 
recompile (live reload) the bean is created
+     * and bound again, replacing the previous one.
      */
     private void postCompile(Class<?> clazz, byte[] byteCode) throws Exception 
{
-        Set<CompilePostProcessor> posts = 
camelContext.getRegistry().findByType(CompilePostProcessor.class);
-        if (posts == null || posts.isEmpty()) {
-            return;
-        }
         // only annotated classes are instantiated: a plain groovy class or 
script is a DTO or a
         // function library, and creating it here would only run its 
constructor for nothing
         if (clazz.getAnnotations().length == 0 || 
Script.class.isAssignableFrom(clazz)) {
             return;
         }
+        Collection<CompilePostProcessor> posts = 
camelContext.getRegistry().findByType(CompilePostProcessor.class);
+        if (posts == null || posts.isEmpty()) {
+            if (defaultPostProcessors == null) {
+                defaultPostProcessors = List.of(
+                        new TypeConverterCompilePostProcessor(),
+                        new EventNotifierCompilePostProcessor(),
+                        new BindToRegistryCompilePostProcessor());
+            }
+            posts = defaultPostProcessors;
+        }
         Object instance = null;
         BindToRegistry bir = clazz.getAnnotation(BindToRegistry.class);
         boolean skip = clazz.isInterface() || 
Modifier.isAbstract(clazz.getModifiers())
diff --git 
a/components/camel-groovy/src/test/java/org/apache/camel/language/groovy/GroovyDefaultCompilePostProcessorTest.java
 
b/components/camel-groovy/src/test/java/org/apache/camel/language/groovy/GroovyDefaultCompilePostProcessorTest.java
new file mode 100644
index 000000000000..c613b8be4769
--- /dev/null
+++ 
b/components/camel-groovy/src/test/java/org/apache/camel/language/groovy/GroovyDefaultCompilePostProcessorTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.groovy;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.CompilePostProcessor;
+import org.apache.camel.spi.SimpleFunction;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Without any {@link CompilePostProcessor} in the registry (as in a 
camel-main, Spring Boot or Quarkus application,
+ * unlike camel-jbang) the compiler falls back to the built-in processors for 
the Camel annotations.
+ */
+public class GroovyDefaultCompilePostProcessorTest extends CamelTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+
+        DefaultGroovyScriptCompiler compiler = new 
DefaultGroovyScriptCompiler();
+        compiler.setCamelContext(context);
+        
compiler.setScriptPattern("file:src/test/resources/camel-groovy-default/*");
+        context.addService(compiler);
+
+        return context;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:mask")
+                        .setBody().simple("${maskEmail(${body})}")
+                        .to("mock:result");
+
+                from("direct:order")
+                        .convertBodyTo(Order.class)
+                        .setBody().simple("${body.id}")
+                        .to("mock:result");
+            }
+        };
+    }
+
+    @Test
+    public void testBindToRegistryWithoutRegisteredPostProcessor() throws 
Exception {
+        
assertTrue(context.getRegistry().findByType(CompilePostProcessor.class).isEmpty());
+        assertInstanceOf(SimpleFunction.class, 
context.getRegistry().lookupByName("mask-email-function"));
+
+        
getMockEndpoint("mock:result").expectedBodiesReceived("j***@example.com");
+        template.sendBody("direct:mask", "[email protected]");
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    @Test
+    public void testConverterWithoutRegisteredPostProcessor() throws Exception 
{
+        assertNotNull(context.getTypeConverterRegistry().lookup(Order.class, 
String.class));
+
+        getMockEndpoint("mock:result").expectedBodiesReceived("123");
+        template.sendBody("direct:order", " 123 ");
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    public record Order(String id) {
+    }
+}
diff --git 
a/components/camel-groovy/src/test/resources/camel-groovy-default/MaskEmailFunction.groovy
 
b/components/camel-groovy/src/test/resources/camel-groovy-default/MaskEmailFunction.groovy
new file mode 100644
index 000000000000..c9e578c534fa
--- /dev/null
+++ 
b/components/camel-groovy/src/test/resources/camel-groovy-default/MaskEmailFunction.groovy
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.camel.BindToRegistry
+import org.apache.camel.Exchange
+import org.apache.camel.spi.SimpleFunction
+
+@BindToRegistry("mask-email-function")
+class MaskEmailFunction implements SimpleFunction {
+
+  String getName() { 'maskEmail' }
+
+  Object apply(Exchange exchange, Object input) {
+    def email = input.toString().trim()
+    int at = email.indexOf('@')
+    if (at <= 0 || at == email.length() - 1) return '***'
+    return email[0] + '***' + email.substring(at)
+  }
+
+}
diff --git 
a/components/camel-groovy/src/test/resources/camel-groovy-default/OrderConverter.groovy
 
b/components/camel-groovy/src/test/resources/camel-groovy-default/OrderConverter.groovy
new file mode 100644
index 000000000000..de8338abc6ad
--- /dev/null
+++ 
b/components/camel-groovy/src/test/resources/camel-groovy-default/OrderConverter.groovy
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import org.apache.camel.Converter
+import 
org.apache.camel.language.groovy.GroovyDefaultCompilePostProcessorTest.Order
+
+@Converter
+class OrderConverter {
+
+    @Converter
+    static Order toOrder(String body) {
+        new Order(body.trim())
+    }
+}
diff --git 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
index 70cc13c280e0..ad5014dfab36 100644
--- 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
+++ 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
@@ -1056,9 +1056,10 @@ In dev mode (`--dev`) an edit of the function, in the 
`.groovy` file or in the Y
 message: the bean is created again on reload and the simple expression 
resolves the function again per evaluation
 when the `dev` profile is active.
 
-NOTE: The `@BindToRegistry` binding of a `.groovy` file is done by the Camel 
CLI. In a project created with
-`camel export` the file is compiled, but the class is not bound as a bean, so 
the function is unknown. For a project
-that is exported, use the inline YAML bean shown above, which works in every 
runtime, or write the function in Java.
+NOTE: The `.groovy` file keeps working in a project created with `camel 
export`: the Groovy compiler in camel-groovy
+binds a `@BindToRegistry` class itself, so the function is available to Camel 
in camel-main, Spring Boot and Quarkus
+alike. The bean lives in Camel's registry, not in the Spring or CDI container, 
so it is not a candidate for
+`@Autowired` or `@Inject`; for that, write the class in Java.
 
 
 == JavaScript Validator
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/compile/BindToRegistryCompilePostProcessor.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/compile/BindToRegistryCompilePostProcessor.java
new file mode 100644
index 000000000000..5740c8ffe1b2
--- /dev/null
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/compile/BindToRegistryCompilePostProcessor.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.support.compile;
+
+import java.util.function.Supplier;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.CamelConfiguration;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Configuration;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.spi.CamelBeanPostProcessor;
+import org.apache.camel.spi.CompilePostProcessor;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.util.ObjectHelper;
+
+/**
+ * Binds a compiled class annotated with {@link BindToRegistry} or {@link 
Configuration} (or that is a
+ * {@link CamelConfiguration}) to the registry, using the {@link 
CamelBeanPostProcessor} so the Camel dependency
+ * injection annotations on the class are processed as well. As the class may 
be compiled again (live reload) the
+ * previous bean is unbound first.
+ */
+public class BindToRegistryCompilePostProcessor implements 
CompilePostProcessor {
+
+    private final boolean lazyBean;
+
+    public BindToRegistryCompilePostProcessor() {
+        this(false);
+    }
+
+    /**
+     * @param lazyBean whether to bind every {@link BindToRegistry} bean 
lazily (created on first use), as if
+     *                 {@link BindToRegistry#lazy()} was set
+     */
+    public BindToRegistryCompilePostProcessor(boolean lazyBean) {
+        this.lazyBean = lazyBean;
+    }
+
+    @Override
+    public void postCompile(CamelContext camelContext, String name, Class<?> 
clazz, byte[] byteCode, Object instance)
+            throws Exception {
+
+        BindToRegistry bir = clazz.getAnnotation(BindToRegistry.class);
+        Configuration cfg = clazz.getAnnotation(Configuration.class);
+
+        // special for lazy beans which we must create on-demand
+        if (instance == null && bir != null && (lazyBean || bir.lazy())) {
+            final String beanName = bir.value();
+            instance = (Supplier<Object>) () -> {
+                Object answer = camelContext.getInjector().newInstance(clazz);
+                CamelBeanPostProcessor bpp = 
PluginHelper.getBeanPostProcessor(camelContext);
+                try {
+                    bpp.postProcessBeforeInitialization(answer, beanName);
+                    bpp.postProcessAfterInitialization(answer, beanName);
+                } catch (Exception e) {
+                    throw RuntimeCamelException.wrapRuntimeException(e);
+                }
+                return answer;
+            };
+            // unbind old bean and register lazy bean
+            camelContext.getRegistry().unbind(beanName);
+            // use dependency injection factory to perform the task of binding 
the bean to registry
+            Runnable task = 
PluginHelper.getDependencyInjectionAnnotationFactory(camelContext)
+                    .createBindToRegistryFactory(name, instance, clazz, 
beanName, false, bir.initMethod(),
+                            bir.destroyMethod());
+            task.run();
+        } else {
+            if (bir != null || cfg != null || instance instanceof 
CamelConfiguration) {
+                CamelBeanPostProcessor bpp = 
PluginHelper.getBeanPostProcessor(camelContext);
+                if (bir != null && ObjectHelper.isNotEmpty(bir.value())) {
+                    name = bir.value();
+                } else if (cfg != null && 
ObjectHelper.isNotEmpty(cfg.value())) {
+                    name = cfg.value();
+                }
+                // to support hot reloading of beans then we need to enable 
unbind mode in bean post processor
+                bpp.setUnbindEnabled(true);
+                try {
+                    // this class uses camels own annotations so the bind to 
registry happens
+                    // automatic by the bean post processor
+                    bpp.postProcessBeforeInitialization(instance, name);
+                    bpp.postProcessAfterInitialization(instance, name);
+                } finally {
+                    bpp.setUnbindEnabled(false);
+                }
+                if (instance instanceof CamelConfiguration cc) {
+                    cc.configure(camelContext);
+                }
+            }
+        }
+    }
+}
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/compile/EventNotifierCompilePostProcessor.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/compile/EventNotifierCompilePostProcessor.java
new file mode 100644
index 000000000000..393678b8e32f
--- /dev/null
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/compile/EventNotifierCompilePostProcessor.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.support.compile;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.CompilePostProcessor;
+import org.apache.camel.spi.EventNotifier;
+import org.apache.camel.spi.ManagementStrategy;
+
+/**
+ * Adds a compiled class that is an {@link EventNotifier} to the {@link 
ManagementStrategy}. As the class may be
+ * compiled again (live reload) the previous notifier of the same name is 
removed first.
+ */
+public class EventNotifierCompilePostProcessor implements CompilePostProcessor 
{
+
+    private final Map<String, EventNotifier> notifiers = new HashMap<>();
+
+    @Override
+    public void postCompile(CamelContext camelContext, String name, Class<?> 
clazz, byte[] byteCode, Object instance)
+            throws Exception {
+        if (instance instanceof EventNotifier en) {
+            ManagementStrategy ms = camelContext.getManagementStrategy();
+            if (ms != null) {
+                notifiers.compute(name, (key, old) -> {
+                    // remove previous instance
+                    if (old != null) {
+                        ms.removeEventNotifier(old);
+                    }
+                    // and new notifier
+                    ms.addEventNotifier(en);
+                    return en;
+                });
+            }
+        }
+    }
+}
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/compile/TypeConverterCompilePostProcessor.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/compile/TypeConverterCompilePostProcessor.java
new file mode 100644
index 000000000000..c25ba37508fd
--- /dev/null
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/compile/TypeConverterCompilePostProcessor.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.support.compile;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Converter;
+import org.apache.camel.LoggingLevel;
+import org.apache.camel.TypeConverterExists;
+import org.apache.camel.spi.CompilePostProcessor;
+import org.apache.camel.spi.TypeConverterRegistry;
+
+/**
+ * Registers a compiled class annotated with {@link Converter} as type 
converters. As the class may be compiled again
+ * (live reload) the converters override any existing ones.
+ */
+public class TypeConverterCompilePostProcessor implements CompilePostProcessor 
{
+
+    @Override
+    public void postCompile(CamelContext camelContext, String name, Class<?> 
clazz, byte[] byteCode, Object instance)
+            throws Exception {
+        if (clazz.isAnnotationPresent(Converter.class)) {
+            TypeConverterRegistry tcr = 
camelContext.getTypeConverterRegistry();
+            TypeConverterExists exists = tcr.getTypeConverterExists();
+            LoggingLevel level = tcr.getTypeConverterExistsLoggingLevel();
+            // force type converter to override as we could be re-loading
+            tcr.setTypeConverterExists(TypeConverterExists.Override);
+            tcr.setTypeConverterExistsLoggingLevel(LoggingLevel.OFF);
+            try {
+                tcr.addTypeConverters(clazz);
+            } finally {
+                tcr.setTypeConverterExists(exists);
+                tcr.setTypeConverterExistsLoggingLevel(level);
+            }
+        }
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 95a24578bb36..a864081edc8f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -140,11 +140,18 @@ script languages and the Camel 4 API use; `request` and 
`in` stay as its older n
 provided its own global variable named `message` is now hidden by the exchange 
variable, like the other exchange
 variable names.
 
-The Groovy script compiler now runs the registered 
`org.apache.camel.spi.CompilePostProcessor`s on a compiled
-class that has class-level annotations, with a new instance of the class, as 
the Java DSL loader does for `.java`
-sources. In the Camel CLI this binds a `@BindToRegistry` class and registers a 
`@Converter` class (and handles the
-Spring and Quarkus annotations) from a `.groovy` file, and binds it again on a 
reload in dev mode. A Groovy class
-without annotations, and a Groovy script, is not instantiated, as before.
+The Groovy script compiler now runs the 
`org.apache.camel.spi.CompilePostProcessor`s on a compiled class that has
+class-level annotations, with a new instance of the class, as the Java DSL 
loader does for `.java` sources. The
+processors in the registry are used when there are any (the Camel CLI 
registers processors that also handle the
+Spring and Quarkus annotations); otherwise built-in processors for the Camel 
annotations are used. So in every runtime
+(camel-main, Spring Boot, Quarkus) a `@BindToRegistry` or `@Configuration` 
class from a `.groovy` file is bound in
+Camel's registry, a `@Converter` class is registered as type converters, and 
an `EventNotifier` class is added to
+the management strategy; in the Camel CLI the bean is bound again on a reload 
in dev mode. A Groovy class without
+annotations, and a Groovy script, is not instantiated, as before.
+
+The Camel-annotation compile post-processors that camel-kamelet-main used 
internally are now public classes in
+camel-support: 
`org.apache.camel.support.compile.BindToRegistryCompilePostProcessor`,
+`TypeConverterCompilePostProcessor` and `EventNotifierCompilePostProcessor`.
 
 === camel-djl (Breaking change)
 
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-beans.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-beans.adoc
index 8bbfa4e7edf5..b68f14438609 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-beans.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-beans.adoc
@@ -79,9 +79,10 @@ NOTE: A `.groovy` file is compiled after the beans of the 
YAML and XML files hav
 `- beans:` entry cannot refer to a Groovy class by its `type`; use 
`@BindToRegistry` on the class instead, or
 create the bean with an inline Groovy `script` in the `- beans:` entry.
 
-NOTE: The annotations on a `.groovy` file are handled by the Camel CLI only. 
In a project created with
-`camel export` the file is compiled, but the class is not bound as a bean. For 
a project that is exported, create
-the bean with an inline Groovy `script` in a `- beans:` entry, which works in 
every runtime, or write it in Java.
+NOTE: The Camel annotations (`@BindToRegistry`, `@Configuration`, 
`@Converter`) on a `.groovy` file keep working in
+a project created with `camel export`, as the Groovy compiler in camel-groovy 
handles them itself in every runtime;
+the Spring and Quarkus annotations are handled by the Camel CLI only. The bean 
lives in Camel's registry, not in the
+Spring or CDI container.
 
 == Defining beans in XML DSL
 
diff --git 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/injection/AnnotationDependencyInjection.java
 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/injection/AnnotationDependencyInjection.java
index 0fcedcdd1f54..cc7f280015da 100644
--- 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/injection/AnnotationDependencyInjection.java
+++ 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/injection/AnnotationDependencyInjection.java
@@ -18,28 +18,20 @@ package org.apache.camel.main.injection;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.function.Supplier;
 
-import org.apache.camel.BindToRegistry;
-import org.apache.camel.CamelConfiguration;
 import org.apache.camel.CamelContext;
-import org.apache.camel.Configuration;
-import org.apache.camel.Converter;
-import org.apache.camel.LoggingLevel;
 import org.apache.camel.NoSuchBeanException;
 import org.apache.camel.RuntimeCamelException;
-import org.apache.camel.TypeConverterExists;
 import org.apache.camel.impl.engine.CamelPostProcessorHelper;
 import org.apache.camel.spi.CamelBeanPostProcessor;
 import org.apache.camel.spi.CamelBeanPostProcessorInjector;
 import org.apache.camel.spi.CompilePostProcessor;
-import org.apache.camel.spi.EventNotifier;
-import org.apache.camel.spi.ManagementStrategy;
 import org.apache.camel.spi.Registry;
-import org.apache.camel.spi.TypeConverterRegistry;
 import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.compile.BindToRegistryCompilePostProcessor;
+import org.apache.camel.support.compile.EventNotifierCompilePostProcessor;
+import org.apache.camel.support.compile.TypeConverterCompilePostProcessor;
 import org.apache.camel.util.AnnotationHelper;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.ReflectionHelper;
@@ -79,7 +71,7 @@ public final class AnnotationDependencyInjection {
         // camel / common
         registry.bind("CamelTypeConverterCompilePostProcessor", new 
TypeConverterCompilePostProcessor());
         registry.bind("CamelEventNotifierCompilePostProcessor", new 
EventNotifierCompilePostProcessor());
-        registry.bind("CamelBindToRegistryCompilePostProcessor", new 
BindToRegistryCompilePostProcessor());
+        registry.bind("CamelBindToRegistryCompilePostProcessor", new 
BindToRegistryCompilePostProcessor(lazyBean));
         // spring
         registry.bind("SpringAnnotationCompilePostProcessor", new 
SpringAnnotationCompilePostProcessor());
         cbbp.addCamelBeanPostProjectInjector(new 
SpringBeanPostProcessorInjector(context));
@@ -88,113 +80,6 @@ public final class AnnotationDependencyInjection {
         cbbp.addCamelBeanPostProjectInjector(new 
QuarkusBeanPostProcessorInjector(context));
     }
 
-    private static class TypeConverterCompilePostProcessor implements 
CompilePostProcessor {
-
-        @Override
-        public void postCompile(CamelContext camelContext, String name, 
Class<?> clazz, byte[] byteCode, Object instance)
-                throws Exception {
-            if (clazz.isAnnotationPresent(Converter.class)) {
-                TypeConverterRegistry tcr = 
camelContext.getTypeConverterRegistry();
-                TypeConverterExists exists = tcr.getTypeConverterExists();
-                LoggingLevel level = tcr.getTypeConverterExistsLoggingLevel();
-                // force type converter to override as we could be re-loading
-                tcr.setTypeConverterExists(TypeConverterExists.Override);
-                tcr.setTypeConverterExistsLoggingLevel(LoggingLevel.OFF);
-                try {
-                    tcr.addTypeConverters(clazz);
-                } finally {
-                    tcr.setTypeConverterExists(exists);
-                    tcr.setTypeConverterExistsLoggingLevel(level);
-                }
-            }
-        }
-    }
-
-    private static class EventNotifierCompilePostProcessor implements 
CompilePostProcessor {
-
-        private final Map<String, EventNotifier> notifiers = new HashMap<>();
-
-        @Override
-        public void postCompile(CamelContext camelContext, String name, 
Class<?> clazz, byte[] byteCode, Object instance)
-                throws Exception {
-            if (instance == null) {
-                return;
-            }
-
-            if (instance instanceof EventNotifier) {
-                ManagementStrategy ms = camelContext.getManagementStrategy();
-                if (ms != null) {
-                    notifiers.compute(name, (key, old) -> {
-                        // remove previous instance
-                        if (old != null) {
-                            ms.removeEventNotifier(old);
-                        }
-                        // and new notifier
-                        EventNotifier en = (EventNotifier) instance;
-                        ms.addEventNotifier(en);
-                        return en;
-                    });
-                }
-            }
-        }
-    }
-
-    private class BindToRegistryCompilePostProcessor implements 
CompilePostProcessor {
-
-        @Override
-        public void postCompile(CamelContext camelContext, String name, 
Class<?> clazz, byte[] byteCode, Object instance)
-                throws Exception {
-
-            BindToRegistry bir = clazz.getAnnotation(BindToRegistry.class);
-            Configuration cfg = clazz.getAnnotation(Configuration.class);
-
-            // special for lazy beans which we must create on-demand
-            if (instance == null && bir != null && (lazyBean || bir.lazy())) {
-                final String beanName = bir.value();
-                instance = (Supplier<Object>) () -> {
-                    Object answer = 
camelContext.getInjector().newInstance(clazz);
-                    CamelBeanPostProcessor bpp = 
PluginHelper.getBeanPostProcessor(camelContext);
-                    try {
-                        bpp.postProcessBeforeInitialization(answer, beanName);
-                        bpp.postProcessAfterInitialization(answer, beanName);
-                    } catch (Exception e) {
-                        throw RuntimeCamelException.wrapRuntimeException(e);
-                    }
-                    return answer;
-                };
-                // unbind old bean and register lazy bean
-                camelContext.getRegistry().unbind(beanName);
-                // use dependency injection factory to perform the task of 
binding the bean to registry
-                Runnable task = 
PluginHelper.getDependencyInjectionAnnotationFactory(camelContext)
-                        .createBindToRegistryFactory(name, instance, clazz, 
beanName, false, bir.initMethod(),
-                                bir.destroyMethod());
-                task.run();
-            } else {
-                if (bir != null || cfg != null || instance instanceof 
CamelConfiguration) {
-                    CamelBeanPostProcessor bpp = 
PluginHelper.getBeanPostProcessor(camelContext);
-                    if (bir != null && ObjectHelper.isNotEmpty(bir.value())) {
-                        name = bir.value();
-                    } else if (cfg != null && 
ObjectHelper.isNotEmpty(cfg.value())) {
-                        name = cfg.value();
-                    }
-                    // to support hot reloading of beans then we need to 
enable unbind mode in bean post processor
-                    bpp.setUnbindEnabled(true);
-                    try {
-                        // this class uses camels own annotations so the bind 
to registry happens
-                        // automatic by the bean post processor
-                        bpp.postProcessBeforeInitialization(instance, name);
-                        bpp.postProcessAfterInitialization(instance, name);
-                    } finally {
-                        bpp.setUnbindEnabled(false);
-                    }
-                    if (instance instanceof CamelConfiguration) {
-                        ((CamelConfiguration) 
instance).configure(camelContext);
-                    }
-                }
-            }
-        }
-    }
-
     private class SpringAnnotationCompilePostProcessor implements 
CompilePostProcessor {
 
         @Override

Reply via email to