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

gnodet pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 470d1086cbd6 CAMEL-25352: fix 
CAMEL_COMPONENT_*/CAMEL_DATAFORMAT_*/CAMEL_LANGUAGE_* env vars ignored and 
ambiguous name lookup
470d1086cbd6 is described below

commit 470d1086cbd688f4d239e3b197c0fca95f3157f9
Author: Guillaume Nodet <[email protected]>
AuthorDate: Wed Oct 7 15:16:15 2026 +0200

    CAMEL-25352: fix CAMEL_COMPONENT_*/CAMEL_DATAFORMAT_*/CAMEL_LANGUAGE_* env 
vars ignored and ambiguous name lookup
    
    - Fix filter prefix mismatch in 
BaseMainSupport.autoConfigurationFromProperties: pass uppercase
      underscore prefixes (CAMEL_COMPONENT_, CAMEL_DATAFORMAT_, 
CAMEL_LANGUAGE_) to filterEnvVariables
      which uppercases keys before comparing, so the previous lowercase dotted 
prefixes never matched
    - Fix non-deterministic name lookup for overlapping catalog names (e.g. 
NETTY vs NETTY_HTTP,
      SJMS vs SJMS2, JS vs JSONPATH): replace startsWith/findFirst on a HashSet 
with a longestMatch
      helper that uses exact boundary check (name + _) and picks the longest 
match deterministically
    - Fix self-defeating workaround examples in upgrade guide (env placeholder 
form does not suppress
      direct env-var auto-binding)
    - Add tests: testLongestMatchComponentEnvVariables, 
testLongestMatchLanguageEnvVariables,
      testLongestMatchDataFormatEnvVariables, testCustomPassPrefixGuard,
      testCamelCaseLanguageEnvVariables, MainEnvVarComponentConfigTest 
(end-to-end)
    - Simplify loadLines: drop unused valueFunc parameter
    - Upgrade guide: document 
autoConfigurationEnvironmentVariablesEnabled=false scope, empty
      variable failure, rename remedy with placeholder reference, and 
custom-named instance limitation
---
 .../org/apache/camel/main/BaseMainSupport.java     |   2 +-
 .../java/org/apache/camel/main/MainHelper.java     |  62 ++++++++---
 .../camel/main/MainEnvVarComponentConfigTest.java  | 103 +++++++++++++++++
 .../java/org/apache/camel/main/MainHelperTest.java | 124 ++++++++++++++++++++-
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |  52 +++++++++
 5 files changed, 319 insertions(+), 24 deletions(-)

diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java 
b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
index 7a094b567152..8799470ba586 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
@@ -3020,7 +3020,7 @@ public abstract class BaseMainSupport extends BaseService 
{
         // load properties from ENV (override existing)
         if 
(mainConfigurationProperties.isAutoConfigurationEnvironmentVariablesEnabled()) {
             Map<String, String> env = MainHelper
-                    .filterEnvVariables(new String[] { "camel.component.", 
"camel.dataformat.", "camel.language." });
+                    .filterEnvVariables(new String[] { "CAMEL_COMPONENT_", 
"CAMEL_DATAFORMAT_", "CAMEL_LANGUAGE_" });
             LOG.debug("Gathered {} ENV variables to configure components, 
dataformats, languages", env.size());
 
             // special configuration when using ENV variables as we need to 
extract the ENV variables
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/MainHelper.java 
b/core/camel-main/src/main/java/org/apache/camel/main/MainHelper.java
index d22967d9706a..524e8b0281b4 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/MainHelper.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/MainHelper.java
@@ -22,6 +22,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.LineNumberReader;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -59,9 +60,9 @@ public final class MainHelper {
 
     private final String version;
     private final StopWatch stopWatch;
-    private final Set<String> componentEnvNames = new HashSet<>();
-    private final Set<String> dataformatEnvNames = new HashSet<>();
-    private final Set<String> languageEnvNames = new HashSet<>();
+    private final Map<String, String> componentEnvNames = new HashMap<>();
+    private final Map<String, String> dataformatEnvNames = new HashMap<>();
+    private final Map<String, String> languageEnvNames = new HashMap<>();
 
     public MainHelper() {
         stopWatch = new StopWatch();
@@ -163,17 +164,20 @@ public final class MainHelper {
         Set<String> toRemove = new HashSet<>();
         env.forEach((k, v) -> {
             if (custom) {
+                if (!k.startsWith("CAMEL_COMPONENT_")) {
+                    return;
+                }
                 toRemove.add(k);
                 String ck = "camel.component." + 
k.substring(16).toLowerCase(Locale.US).replace('_', '-');
                 ck = ck.replaceFirst("-", ".");
                 properties.put(ck, v);
             } else {
-                Optional<String> e
-                        = 
componentEnvNames.stream().filter(k::startsWith).findFirst();
+                Optional<Map.Entry<String, String>> e = 
longestMatch(componentEnvNames, k);
                 if (e.isPresent()) {
                     toRemove.add(k);
-                    String cname = "camel.component." + 
e.get().substring(16).toLowerCase(Locale.US).replace('_', '-');
-                    String option = k.substring(cname.length() + 
1).toLowerCase(Locale.US).replace('_', '-');
+                    String catalogName = e.get().getValue();
+                    String cname = "camel.component." + catalogName;
+                    String option = k.substring(e.get().getKey().length() + 
1).toLowerCase(Locale.US).replace('_', '-');
                     properties.put(cname + "." + option, v);
                 }
             }
@@ -185,17 +189,20 @@ public final class MainHelper {
         Set<String> toRemove = new HashSet<>();
         env.forEach((k, v) -> {
             if (custom) {
+                if (!k.startsWith("CAMEL_DATAFORMAT_")) {
+                    return;
+                }
                 toRemove.add(k);
                 String ck = "camel.dataformat." + 
k.substring(17).toLowerCase(Locale.US).replace('_', '-');
                 ck = ck.replaceFirst("-", ".");
                 properties.put(ck, v);
             } else {
-                Optional<String> e
-                        = 
dataformatEnvNames.stream().filter(k::startsWith).findFirst();
+                Optional<Map.Entry<String, String>> e = 
longestMatch(dataformatEnvNames, k);
                 if (e.isPresent()) {
                     toRemove.add(k);
-                    String cname = "camel.dataformat." + 
e.get().substring(17).toLowerCase(Locale.US).replace('_', '-');
-                    String option = k.substring(cname.length() + 
1).toLowerCase(Locale.US).replace('_', '-');
+                    String catalogName = e.get().getValue();
+                    String cname = "camel.dataformat." + catalogName;
+                    String option = k.substring(e.get().getKey().length() + 
1).toLowerCase(Locale.US).replace('_', '-');
                     properties.put(cname + "." + option, v);
                 }
             }
@@ -207,17 +214,20 @@ public final class MainHelper {
         Set<String> toRemove = new HashSet<>();
         env.forEach((k, v) -> {
             if (custom) {
+                if (!k.startsWith("CAMEL_LANGUAGE_")) {
+                    return;
+                }
                 toRemove.add(k);
                 String ck = "camel.language." + 
k.substring(15).toLowerCase(Locale.US).replace('_', '-');
                 ck = ck.replaceFirst("-", ".");
                 properties.put(ck, v);
             } else {
-                Optional<String> e
-                        = 
languageEnvNames.stream().filter(k::startsWith).findFirst();
+                Optional<Map.Entry<String, String>> e = 
longestMatch(languageEnvNames, k);
                 if (e.isPresent()) {
                     toRemove.add(k);
-                    String cname = "camel.language." + 
e.get().substring(15).toLowerCase(Locale.US).replace('_', '-');
-                    String option = k.substring(cname.length() + 
1).toLowerCase(Locale.US).replace('_', '-');
+                    String catalogName = e.get().getValue();
+                    String cname = "camel.language." + catalogName;
+                    String option = k.substring(e.get().getKey().length() + 
1).toLowerCase(Locale.US).replace('_', '-');
                     properties.put(cname + "." + option, v);
                 }
             }
@@ -476,6 +486,21 @@ public final class MainHelper {
         }
     }
 
+    /**
+     * Returns the longest name from {@code names} such that {@code envVarKey} 
starts with that name followed by
+     * {@code _}, or {@link Optional#empty()} if no such name exists.
+     * <p>
+     * Using the longest match is critical when the catalog contains prefix 
pairs such as {@code CAMEL_COMPONENT_NETTY}
+     * / {@code CAMEL_COMPONENT_NETTY_HTTP} or {@code CAMEL_LANGUAGE_JS} / 
{@code CAMEL_LANGUAGE_JSONPATH}: a simple
+     * {@code findFirst()} on a {@link java.util.HashSet} gives a 
non-deterministic result, whereas the longest match
+     * always selects the most specific name.
+     */
+    private static Optional<Map.Entry<String, String>> 
longestMatch(Map<String, String> names, String envVarKey) {
+        return names.entrySet().stream()
+                .filter(e -> envVarKey.startsWith(e.getKey() + "_"))
+                .max(Comparator.comparingInt(e -> e.getKey().length()));
+    }
+
     /**
      * Loads the entire stream into memory as a String and returns it.
      * <p/>
@@ -483,13 +508,16 @@ public final class MainHelper {
      * <p/>
      * Warning, don't use for crazy big streams :)
      */
-    private static void loadLines(InputStream in, Set<String> lines, 
Function<String, String> func) throws IOException {
+    private static void loadLines(
+            InputStream in, Map<String, String> map,
+            Function<String, String> keyFunc)
+            throws IOException {
         if (in != null) {
             try (final InputStreamReader isr = new InputStreamReader(in);
                  final BufferedReader reader = new LineNumberReader(isr)) {
                 String line;
                 while ((line = reader.readLine()) != null) {
-                    lines.add(func.apply(line));
+                    map.put(keyFunc.apply(line), line);
                 }
             }
         }
diff --git 
a/core/camel-main/src/test/java/org/apache/camel/main/MainEnvVarComponentConfigTest.java
 
b/core/camel-main/src/test/java/org/apache/camel/main/MainEnvVarComponentConfigTest.java
new file mode 100644
index 000000000000..ee287abf6fd3
--- /dev/null
+++ 
b/core/camel-main/src/test/java/org/apache/camel/main/MainEnvVarComponentConfigTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.main;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.seda.SedaComponent;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+import org.junit.jupiter.api.parallel.Isolated;
+import org.junit.jupiter.api.parallel.ResourceLock;
+import org.junit.jupiter.api.parallel.Resources;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * End-to-end test for {@code CAMEL_COMPONENT_*} environment variable 
processing through {@link BaseMainSupport}.
+ * Verifies that the bug-1 fix (using uppercase prefixes in {@code 
filterEnvVariables}) actually reaches the component
+ * at runtime.
+ * <p>
+ * Reverting the one-line change in {@code BaseMainSupport} (back to lowercase 
{@code "camel.component."}) leaves every
+ * {@link MainHelperTest} green but breaks this test, because only this test 
exercises the full
+ * {@code filterEnvVariables → addComponentEnvVariables} path inside {@code 
BaseMainSupport}.
+ */
+@Isolated
+@ResourceLock(Resources.SYSTEM_PROPERTIES)
+@DisabledOnOs(OS.WINDOWS)
+public class MainEnvVarComponentConfigTest {
+
+    private String previousValue;
+
+    @Test
+    public void testCamelComponentEnvVarConfiguresComponent() {
+        // Inject CAMEL_COMPONENT_SEDA_QUEUE_SIZE=100 into the process 
environment
+        setEnv("CAMEL_COMPONENT_SEDA_QUEUE_SIZE", "100");
+
+        Main main = new Main();
+        main.configure().addRoutesBuilder(new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").to("seda:test");
+            }
+        });
+        main.start();
+
+        try {
+            SedaComponent seda = main.getCamelContext().getComponent("seda", 
SedaComponent.class);
+            assertNotNull(seda);
+            assertEquals(100, seda.getQueueSize(),
+                    "CAMEL_COMPONENT_SEDA_QUEUE_SIZE=100 must configure 
seda.queueSize via BaseMainSupport");
+        } finally {
+            main.stop();
+        }
+    }
+
+    @AfterEach
+    void tearDown() {
+        // Restore original env state
+        if (previousValue != null) {
+            getEditableEnv().put("CAMEL_COMPONENT_SEDA_QUEUE_SIZE", 
previousValue);
+        } else {
+            getEditableEnv().remove("CAMEL_COMPONENT_SEDA_QUEUE_SIZE");
+        }
+    }
+
+    private void setEnv(String name, String value) {
+        Map<String, String> env = getEditableEnv();
+        previousValue = env.get(name);
+        env.put(name, value);
+    }
+
+    private static Map<String, String> getEditableEnv() {
+        Class<?> classOfMap = System.getenv().getClass();
+        try {
+            Field field = classOfMap.getDeclaredField("m");
+            field.setAccessible(true);
+            @SuppressWarnings("unchecked")
+            Map<String, String> m = (Map<String, String>) 
field.get(System.getenv());
+            return m;
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new RuntimeException("Cannot access System.getenv() map", e);
+        }
+    }
+}
diff --git 
a/core/camel-main/src/test/java/org/apache/camel/main/MainHelperTest.java 
b/core/camel-main/src/test/java/org/apache/camel/main/MainHelperTest.java
index d928f15aa5dc..d073f74dfda1 100644
--- a/core/camel-main/src/test/java/org/apache/camel/main/MainHelperTest.java
+++ b/core/camel-main/src/test/java/org/apache/camel/main/MainHelperTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.main;
 
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 
@@ -23,12 +24,12 @@ import org.apache.camel.util.OrderedProperties;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
-public class MainHelperTest {
+class MainHelperTest {
 
     private final MainHelper helper = new MainHelper();
 
     @Test
-    public void testAddComponentEnvVariables() {
+    void testAddComponentEnvVariables() {
         Map<String, String> env = MainHelper.filterEnvVariables(new String[] { 
"CAMEL_COMPONENT_" });
         env.put("CAMEL_COMPONENT_AWS2_S3_ACCESS_KEY", "mysecretkey");
         Properties prop = new OrderedProperties();
@@ -40,7 +41,7 @@ public class MainHelperTest {
     }
 
     @Test
-    public void testAddDataFormatEnvVariables() {
+    void testAddDataFormatEnvVariables() {
         Map<String, String> env = MainHelper.filterEnvVariables(new String[] { 
"CAMEL_DATAFORMAT_" });
         env.put("CAMEL_DATAFORMAT_BASE64_LINE_LENGTH", "64");
         env.put("CAMEL_DATAFORMAT_JACKSONXML_PRETTYPRINT", "true");
@@ -50,11 +51,11 @@ public class MainHelperTest {
         Assertions.assertEquals(0, env.size());
         Assertions.assertEquals(2, prop.size());
         Assertions.assertEquals("64", 
prop.getProperty("camel.dataformat.base64.line-length"));
-        Assertions.assertEquals("true", 
prop.getProperty("camel.dataformat.jacksonxml.prettyprint"));
+        Assertions.assertEquals("true", 
prop.getProperty("camel.dataformat.jacksonXml.prettyprint"));
     }
 
     @Test
-    public void testAddLanguageEnvVariables() {
+    void testAddLanguageEnvVariables() {
         Map<String, String> env = MainHelper.filterEnvVariables(new String[] { 
"CAMEL_LANGUAGE_" });
         env.put("CAMEL_LANGUAGE_JAVA_PRE_COMPILE", "false");
         Properties prop = new OrderedProperties();
@@ -66,7 +67,7 @@ public class MainHelperTest {
     }
 
     @Test
-    public void testAddCustomComponentEnvVariables() {
+    void testAddCustomComponentEnvVariables() {
         Map<String, String> env = MainHelper.filterEnvVariables(new String[] { 
"CAMEL_COMPONENT_" });
         env.put("CAMEL_COMPONENT_AWS2_S3_ACCESS_KEY", "mysecretkey");
         env.put("CAMEL_COMPONENT_FOO_VERBOSE", "true");
@@ -87,4 +88,115 @@ public class MainHelperTest {
         Assertions.assertEquals("false", 
prop.getProperty("camel.component.foo.pretty-print"));
     }
 
+    /**
+     * Verifies that overlapping component names (e.g. NETTY vs NETTY_HTTP, 
SJMS vs SJMS2, FILE vs FILE_WATCH) are
+     * resolved by longest-match so that each variable is mapped to the 
correct component.
+     */
+    @Test
+    void testLongestMatchComponentEnvVariables() {
+        Map<String, String> env = MainHelper.filterEnvVariables(new String[] { 
"CAMEL_COMPONENT_" });
+        // netty-http must not be mapped to netty
+        env.put("CAMEL_COMPONENT_NETTY_HTTP_MUTE_EXCEPTION", "true");
+        // sjms2 must not be mapped to sjms
+        env.put("CAMEL_COMPONENT_SJMS2_RECOVERY_INTERVAL", "2000");
+        // file-watch must not be mapped to file
+        env.put("CAMEL_COMPONENT_FILE_WATCH_QUEUE_SIZE", "5000");
+        // netty itself (no suffix clash)
+        env.put("CAMEL_COMPONENT_NETTY_RECEIVE_BUFFER_SIZE", "65536");
+
+        Properties prop = new OrderedProperties();
+        helper.addComponentEnvVariables(env, prop, false);
+
+        Assertions.assertEquals(0, env.size());
+        Assertions.assertEquals(4, prop.size());
+        Assertions.assertEquals("true", 
prop.getProperty("camel.component.netty-http.mute-exception"));
+        Assertions.assertEquals("2000", 
prop.getProperty("camel.component.sjms2.recovery-interval"));
+        Assertions.assertEquals("5000", 
prop.getProperty("camel.component.file-watch.queue-size"));
+        Assertions.assertEquals("65536", 
prop.getProperty("camel.component.netty.receive-buffer-size"));
+    }
+
+    /**
+     * Verifies that overlapping language names (e.g. JS vs JSONPATH) are 
resolved by longest-match.
+     */
+    @Test
+    void testLongestMatchLanguageEnvVariables() {
+        Map<String, String> env = MainHelper.filterEnvVariables(new String[] { 
"CAMEL_LANGUAGE_" });
+        // jsonpath must not be mapped to js
+        env.put("CAMEL_LANGUAGE_JSONPATH_SUPPRESS_EXCEPTIONS", "true");
+
+        Properties prop = new OrderedProperties();
+        helper.addLanguageEnvVariables(env, prop, false);
+
+        Assertions.assertEquals(0, env.size());
+        Assertions.assertEquals(1, prop.size());
+        Assertions.assertEquals("true", 
prop.getProperty("camel.language.jsonpath.suppress-exceptions"));
+    }
+
+    /**
+     * Verifies that overlapping dataformat names (e.g. AVRO vs AVROJACKSON) 
are resolved by longest-match.
+     */
+    @Test
+    void testLongestMatchDataFormatEnvVariables() {
+        Map<String, String> env = MainHelper.filterEnvVariables(new String[] { 
"CAMEL_DATAFORMAT_" });
+        // avroJackson must not be mapped to avro
+        env.put("CAMEL_DATAFORMAT_AVROJACKSON_AUTO_DISCOVER_OBJECT_MAPPER", 
"true");
+        // avro itself
+        env.put("CAMEL_DATAFORMAT_AVRO_INSTANCE_CLASS_NAME", 
"com.example.MySchema");
+
+        Properties prop = new OrderedProperties();
+        helper.addDataFormatEnvVariables(env, prop, false);
+
+        Assertions.assertEquals(0, env.size());
+        Assertions.assertEquals(2, prop.size());
+        Assertions.assertEquals("true", 
prop.getProperty("camel.dataformat.avroJackson.auto-discover-object-mapper"));
+        Assertions.assertEquals("com.example.MySchema", 
prop.getProperty("camel.dataformat.avro.instance-class-name"));
+    }
+
+    /**
+     * Verifies that the custom pass only takes its own prefix: 
CAMEL_LANGUAGE_* and CAMEL_DATAFORMAT_* leftovers must
+     * not bleed into the component bucket.
+     */
+    @Test
+    void testCustomPassPrefixGuard() {
+        // Simulate: catalog pass consumed aws2-s3 but left unknowns for other 
types
+        Map<String, String> env = new HashMap<>();
+        env.put("CAMEL_LANGUAGE_FOO_BAR", "x");
+        env.put("CAMEL_DATAFORMAT_FOO_BAR", "y");
+
+        Properties prop = new OrderedProperties();
+        // component custom pass: must not consume CAMEL_LANGUAGE_* or 
CAMEL_DATAFORMAT_*
+        helper.addComponentEnvVariables(env, prop, true);
+        Assertions.assertEquals(2, env.size(),
+                "CAMEL_LANGUAGE_* and CAMEL_DATAFORMAT_* must be left in env 
after component custom pass");
+        Assertions.assertEquals(0, prop.size(), "No camel.component.* must be 
produced from non-component prefixes");
+
+        // dataformat custom pass: must consume CAMEL_DATAFORMAT_* only
+        helper.addDataFormatEnvVariables(env, prop, true);
+        Assertions.assertEquals(1, env.size(), "CAMEL_LANGUAGE_* must remain 
after dataformat custom pass");
+        Assertions.assertEquals("y", 
prop.getProperty("camel.dataformat.foo.bar"));
+
+        // language custom pass: must consume CAMEL_LANGUAGE_* only
+        helper.addLanguageEnvVariables(env, prop, true);
+        Assertions.assertEquals(0, env.size());
+        Assertions.assertEquals("x", 
prop.getProperty("camel.language.foo.bar"));
+    }
+
+    /**
+     * Verifies that camelCase language names (e.g. exchangeProperty) are 
preserved through the catalog map rather than
+     * lowercased.
+     */
+    @Test
+    void testCamelCaseLanguageEnvVariables() {
+        Map<String, String> env = MainHelper.filterEnvVariables(new String[] { 
"CAMEL_LANGUAGE_" });
+        // exchangeProperty is a camelCase language name — must not become 
"exchangeproperty"
+        env.put("CAMEL_LANGUAGE_EXCHANGEPROPERTY_TRIM", "false");
+
+        Properties prop = new OrderedProperties();
+        helper.addLanguageEnvVariables(env, prop, false);
+
+        Assertions.assertEquals(0, env.size());
+        Assertions.assertEquals(1, prop.size());
+        Assertions.assertEquals("false", 
prop.getProperty("camel.language.exchangeProperty.trim"));
+    }
+
 }
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 443976a4fa07..639c5fdd6ee7 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
@@ -3886,6 +3886,58 @@ When `camel.opentelemetry.*` properties are configured 
but OpenTelemetry is disa
 the `camel.opentelemetry2.*` or `camel.telemetryDev.*` properties are now 
used. Previously they were ignored whenever
 any `camel.opentelemetry.*` property was configured.
 
+=== camel-main - `CAMEL_COMPONENT_*`, `CAMEL_DATAFORMAT_*`, `CAMEL_LANGUAGE_*` 
environment variables now take effect
+
+Two bugs in `camel-main`'s environment-variable processing were fixed and are 
now active.
+
+**Bug 1 — variables were silently ignored since Camel 3.9 (CAMEL-16345)**
+
+`filterEnvVariables` uppercases the variable names before comparing them 
against the supplied prefixes, so the prefixes
+themselves must be uppercase. Before this fix the code passed lowercase 
prefixes (`camel.component.`, etc.), causing
+every match to fail silently. This has been corrected: `CAMEL_COMPONENT_*`, 
`CAMEL_DATAFORMAT_*` and `CAMEL_LANGUAGE_*`
+variables are now actually read.
+
+If your deployments intentionally set such variables but relied on them being 
ignored (for example to pass secrets that
+should not reach the component), remove the variables or set
+`camel.main.autoConfigurationEnvironmentVariablesEnabled=false` to opt out 
(note: this also disables `CAMEL_MAIN_*`
+and all other ENV-based configuration in `BaseMainSupport`).
+
+Variables that refer to a name not present on the classpath (for example 
`CAMEL_COMPONENT_KAFKA_BROKERS` in an
+application that does not include camel-kafka) will fail the startup with an 
`IllegalArgumentException`. Variables with
+an empty value (for example `CAMEL_COMPONENT_SEDA_QUEUE_SIZE=` from an unset 
compose variable) will fail with
+_value is empty_. There is no per-variable opt-out: rename the variable, 
remove it, or set
+`camel.main.autoConfigurationEnvironmentVariablesEnabled=false` (note: this 
also disables `CAMEL_MAIN_*` and all other
+ENV-based configuration in `BaseMainSupport`).
+
+Secrets passed as component environment variables (for example 
`CAMEL_COMPONENT_AWS2_S3_SECRET_KEY`) will be seen by
+the `prod`-profile security check as plain-text secrets and will fail the 
startup. The
+`camel.component.aws2-s3.secret-key={{env:CAMEL_COMPONENT_AWS2_S3_SECRET_KEY}}`
 placeholder form does not help —
+`filterEnvVariables` still picks up the original variable and binds its value 
directly. To pass a secret without
+triggering the security check, either rename the environment variable to a 
name that does not match the
+`CAMEL_COMPONENT_*` pattern and reference it with a placeholder (for example
+`camel.component.aws2-s3.secret-key={{env:MY_AWS_SECRET_KEY}}`), configure the 
value through a vault, or disable
+auto-configuration from environment variables with 
`camel.main.autoConfigurationEnvironmentVariablesEnabled=false`
+(which also turns off the `CAMEL_MAIN_*` variables).
+
+**Bug 2 — ambiguous name resolution**
+
+When the catalog contained component/dataformat/language pairs whose names 
were prefixes of each other (such as `netty`
+/ `netty-http`, `js` / `jsonpath`, `avro` / `avroJackson`), the variable was 
randomly mapped to either name.
+A longest-match strategy now always selects the most specific name.
+
+**Known limitation — `rest` / `rest-api` and custom-named instances**
+
+`CAMEL_COMPONENT_REST_API_DOC=api.json` is matched to 
`camel.component.rest-api.doc` (the longer match), not to
+`camel.component.rest.apiDoc`. Use `CAMEL_COMPONENT_REST_APIDOC=api.json` 
instead (option names are matched
+case-insensitively). Note that using 
`camel.component.rest.apiDoc={{env:CAMEL_COMPONENT_REST_API_DOC}}` does not work
+as a workaround — `filterEnvVariables` still matches the original variable and 
binds it to `rest-api.doc` in addition
+to the placeholder.
+
+The same limitation affects custom-named component instances whose name starts 
with a catalog name. For example, with a
+component registered as `kafka-dr`, `CAMEL_COMPONENT_KAFKA_DR_BROKERS` is 
matched to `kafka` (the catalog entry) and
+becomes `camel.component.kafka.dr-brokers`, an unknown option that fails the 
startup. Configure such instances through
+`application.properties` instead: 
`camel.component.kafka-dr.brokers={{env:KAFKA_DR_BROKERS}}`.
+
 === camel-seda - stopping a suspended route does not wait for its pending 
messages
 
 Stopping a suspended SEDA route, or stopping the CamelContext while such a 
route is suspended, no longer waits for the

Reply via email to