diff --git a/components/camel-undertow/pom.xml b/components/camel-undertow/pom.xml
index 5ed99a6b734e0..eb7010762def7 100644
--- a/components/camel-undertow/pom.xml
+++ b/components/camel-undertow/pom.xml
@@ -133,6 +133,11 @@
${awaitility-version}
test
+
+ org.assertj
+ assertj-core
+ test
+
org.eclipse.jetty.http2
jetty-http2-client
diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java
index 358c154d1f7ab..d97ae90edb266 100644
--- a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java
+++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java
@@ -37,7 +37,6 @@
import org.apache.camel.component.undertow.UndertowConstants.EventType;
import org.apache.camel.component.undertow.handlers.CamelWebSocketHandler;
import org.apache.camel.component.undertow.spi.UndertowSecurityProvider;
-import org.apache.camel.http.base.HttpHeaderFilterStrategy;
import org.apache.camel.http.base.OAuthHttpSecuritySupport;
import org.apache.camel.http.base.OAuthProfileAwareHttpEndpoint;
import org.apache.camel.http.base.cookie.CookieHandler;
@@ -85,7 +84,7 @@ public class UndertowEndpoint extends DefaultEndpoint
@UriParam(label = "advanced")
private AccessLogReceiver accessLogReceiver;
@UriParam(label = "advanced")
- private HeaderFilterStrategy headerFilterStrategy = new HttpHeaderFilterStrategy();
+ private HeaderFilterStrategy headerFilterStrategy = new UndertowHeaderFilterStrategy();
@UriParam(label = "security")
private SSLContextParameters sslContextParameters;
@UriParam(label = "consumer")
diff --git a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowEndpointTest.java b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowEndpointTest.java
index 817a13a9da7fc..75a8be7ffd02e 100644
--- a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowEndpointTest.java
+++ b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowEndpointTest.java
@@ -18,9 +18,12 @@
import java.net.URI;
+import org.apache.camel.http.base.HttpHeaderFilterStrategy;
+import org.apache.camel.spi.HeaderFilterStrategy;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class UndertowEndpointTest {
@@ -47,4 +50,35 @@ public void nonEmptyPathShouldBeKeptSame() {
endpoint.setHttpURI(withSlash);
assertEquals(withSlash, endpoint.getHttpURI());
}
+
+ @Test
+ void defaultHeaderFilterStrategyIsUndertowSpecific() {
+ assertThat(endpoint.getHeaderFilterStrategy()).isInstanceOf(UndertowHeaderFilterStrategy.class);
+ }
+
+ @Test
+ void defaultBindingKeepsUndertowHeaderFilterStrategy() {
+ // the endpoint pushes its own strategy into the lazily created binding, so the endpoint default
+ // decides which strategy the binding ends up running
+ assertThat(endpoint.getUndertowHttpBinding()).isInstanceOf(DefaultUndertowHttpBinding.class);
+ HeaderFilterStrategy strategy
+ = ((DefaultUndertowHttpBinding) endpoint.getUndertowHttpBinding()).getHeaderFilterStrategy();
+ assertThat(strategy).isInstanceOf(UndertowHeaderFilterStrategy.class);
+
+ // the undertow-specific prefixes added by CAMEL-23588 must therefore be in effect
+ assertThat(strategy.applyFilterToExternalHeaders(UndertowConstants.CONNECTION_KEY, "aValue", null)).isTrue();
+ assertThat(strategy.applyFilterToExternalHeaders(UndertowConstants.CONNECTION_KEY_LIST, "aValue", null)).isTrue();
+ assertThat(strategy.applyFilterToExternalHeaders(UndertowConstants.SEND_TO_ALL, "aValue", null)).isTrue();
+ assertThat(strategy.applyFilterToCamelHeaders(UndertowConstants.CONNECTION_KEY, "aValue", null)).isTrue();
+ }
+
+ @Test
+ void explicitHeaderFilterStrategyIsHandedToTheBinding() {
+ HeaderFilterStrategy custom = new HttpHeaderFilterStrategy();
+ endpoint.setHeaderFilterStrategy(custom);
+
+ assertThat(endpoint.getUndertowHttpBinding()).isInstanceOf(DefaultUndertowHttpBinding.class);
+ assertThat(((DefaultUndertowHttpBinding) endpoint.getUndertowHttpBinding()).getHeaderFilterStrategy())
+ .isSameAs(custom);
+ }
}
diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index a6b8fb0663265..5bb33211e540a 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -1619,3 +1619,35 @@ only once.
In a Spring Boot application, Spring AI's auto-configured `ToolCallbackResolver` is built from the
`ToolCallback` and `ToolCallbackProvider` beans in the context. A bare `@Bean Function<...>` is no
longer resolvable by bean name; expose such tools as `ToolCallback` or `ToolCallbackProvider` beans.
+
+=== camel-undertow - UndertowHeaderFilterStrategy is now the endpoint default
+
+`UndertowEndpoint` defaulted its `headerFilterStrategy` to the base
+`HttpHeaderFilterStrategy`, and pushed that strategy into the `DefaultUndertowHttpBinding`
+it creates lazily, overwriting the `UndertowHeaderFilterStrategy` that the binding installs
+in its own constructor. The undertow-specific filtering was therefore not applied on
+endpoint-configured routes.
+
+The endpoint now defaults to `UndertowHeaderFilterStrategy`, which makes two already
+documented behaviours take effect:
+
+* The legacy `websocket.*` Exchange-header prefix, added to the in and out filters in
+ 4.14.8 / 4.18.3 / 4.21.0 (see the 4.18 upgrade guide), is now filtered at the undertow
+ transport boundary as described there.
+* Header names that undertow does not accept (those for which
+ `io.undertow.util.HttpString.tryFromString` returns `null`) are skipped when mapping
+ external headers in, rather than being mapped onto the message.
+
+Ordinary application headers are unaffected, and Rest DSL consumers already used an
+undertow-specific strategy (`UndertowRestHeaderFilterStrategy`) so their behaviour does not
+change. Routes that relied on `websocket.*` headers crossing the undertow boundary in either
+direction, and routes that relied on undertow-invalid header names being mapped, can restore
+the previous behaviour by configuring `headerFilterStrategy` explicitly on the endpoint:
+
+[source,java]
+----
+from("undertow:http://0.0.0.0:8080/foo?headerFilterStrategy=#myStrategy")
+----
+
+Routes that already supply a custom `headerFilterStrategy` or a custom `undertowHttpBinding`
+are unaffected.