You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2026/10/06 09:00:29 UTC

(camel) branch camel-4.22.x updated: [backport camel-4.22.x] CAMEL-25340: camel-spring-ws - apply the HeaderFilterStrategy to outbound SOAP headers (#27423)

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

oscerd pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.22.x by this push:
     new d53eb7feecde [backport camel-4.22.x] CAMEL-25340: camel-spring-ws - apply the HeaderFilterStrategy to outbound SOAP headers (#27423)
d53eb7feecde is described below

commit d53eb7feecde9ae64e0c7a56e2e03d04556cf56d
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Tue Oct 6 11:00:21 2026 +0200

    [backport camel-4.22.x] CAMEL-25340: camel-spring-ws - apply the HeaderFilterStrategy to outbound SOAP headers (#27423)
    
    Straight cherry-pick of #27377 (main) to camel-4.22.x. The default BasicMessageFilter now applies the endpoint's headerFilterStrategy before writing message headers into SOAP headers, on both the producer request and the consumer response, so Camel/camel-prefixed headers are no longer written by default.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <no...@anthropic.com>
    Signed-off-by: Andrea Cosentino <an...@gmail.com>
---
 .../apache/camel/catalog/components/spring-ws.json |   2 +-
 .../camel/catalog/docs/spring-ws-component.adoc    |  11 ++
 .../camel/component/spring/ws/spring-ws.json       |   2 +-
 .../src/main/docs/spring-ws-component.adoc         |  11 ++
 .../spring/ws/SpringWebserviceComponent.java       |   4 +-
 .../spring/ws/SpringWebserviceConfiguration.java   |   2 +-
 .../spring/ws/filter/impl/BasicMessageFilter.java  |  43 +++++++-
 .../spring/ws/OutboundSoapHeaderFilterTest.java    | 120 +++++++++++++++++++++
 .../ws/filter/impl/BasicMessageFilterTest.java     |  58 ++++++++++
 .../ws/OutboundSoapHeaderFilterTest-context.xml    |  84 +++++++++++++++
 .../SpringWebserviceEndpointBuilderFactory.java    |  18 ++--
 11 files changed, 343 insertions(+), 12 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-ws.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-ws.json
index 106c37c92bb5..9b0f45477777 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-ws.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-ws.json
@@ -43,7 +43,7 @@
     "type": { "index": 0, "kind": "path", "displayName": "Type", "group": "consumer", "label": "consumer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.spring.ws.type.EndpointMappingType", "enum": [ "ROOT_QNAME", "ACTION", "TO", "SOAP_ACTION", "XPATHRESULT", "URI", "URI_PATH", "BEANNAME" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField" [...]
     "lookupKey": { "index": 1, "kind": "path", "displayName": "Lookup Key", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "Endpoint mapping key if endpoint mapping is used" },
     "webServiceEndpointUri": { "index": 2, "kind": "path", "displayName": "Web Service Endpoint Uri", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "The default Web Service endpoint uri to use for the producer." },
-    "headerFilterStrategy": { "index": 3, "kind": "parameter", "displayName": "Header Filter Strategy", "group": "common", "label": "common", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "To use a custom HeaderFilterStrategy to filter head [...]
+    "headerFilterStrategy": { "index": 3, "kind": "parameter", "displayName": "Header Filter Strategy", "group": "common", "label": "common", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "To use a custom HeaderFilterStrategy to filter head [...]
     "messageFilter": { "index": 4, "kind": "parameter", "displayName": "Message Filter", "group": "common", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.spring.ws.filter.MessageFilter", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "Option to provide a custom MessageFilter. For example wh [...]
     "messageIdStrategy": { "index": 5, "kind": "parameter", "displayName": "Message Id Strategy", "group": "common", "label": "common", "required": false, "type": "object", "javaType": "org.springframework.ws.soap.addressing.messageid.MessageIdStrategy", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "Option to provide a custom Messa [...]
     "endpointDispatcher": { "index": 6, "kind": "parameter", "displayName": "Endpoint Dispatcher", "group": "consumer", "label": "consumer", "required": false, "type": "object", "javaType": "org.apache.camel.component.spring.ws.bean.CamelEndpointDispatcher", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "Spring org.springframework.w [...]
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ws-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ws-component.adoc
index 2f83dc5c50a3..009ccd11adba 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ws-component.adoc
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ws-component.adoc
@@ -335,6 +335,17 @@ protected void doProcessSoapAttachements(Message inOrOut, SoapMessage response)
 }
 ----
 
+The `BasicMessageFilter` that the component creates for an endpoint
+applies the endpoint's `headerFilterStrategy` to the message headers it
+writes into the SOAP header, so by default headers whose names start
+with `Camel` or `camel` are not written. A message filter you provide
+yourself, global or local, does not know which endpoint it runs for: a
+`BasicMessageFilter`, or a subclass of it, created with the no-argument
+constructor applies a `SpringWebserviceHeaderFilterStrategy`, whatever
+`headerFilterStrategy` the endpoint is configured with. To apply another
+strategy, create it with the `BasicMessageFilter(Supplier<HeaderFilterStrategy>)`
+constructor, or override `getHeaderFilterStrategy()`.
+
 === Using a custom MessageSender and MessageFactory
 
 A custom message sender or factory in the registry can be referenced
diff --git a/components/camel-spring-parent/camel-spring-ws/src/generated/resources/META-INF/org/apache/camel/component/spring/ws/spring-ws.json b/components/camel-spring-parent/camel-spring-ws/src/generated/resources/META-INF/org/apache/camel/component/spring/ws/spring-ws.json
index 106c37c92bb5..9b0f45477777 100644
--- a/components/camel-spring-parent/camel-spring-ws/src/generated/resources/META-INF/org/apache/camel/component/spring/ws/spring-ws.json
+++ b/components/camel-spring-parent/camel-spring-ws/src/generated/resources/META-INF/org/apache/camel/component/spring/ws/spring-ws.json
@@ -43,7 +43,7 @@
     "type": { "index": 0, "kind": "path", "displayName": "Type", "group": "consumer", "label": "consumer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.spring.ws.type.EndpointMappingType", "enum": [ "ROOT_QNAME", "ACTION", "TO", "SOAP_ACTION", "XPATHRESULT", "URI", "URI_PATH", "BEANNAME" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField" [...]
     "lookupKey": { "index": 1, "kind": "path", "displayName": "Lookup Key", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "Endpoint mapping key if endpoint mapping is used" },
     "webServiceEndpointUri": { "index": 2, "kind": "path", "displayName": "Web Service Endpoint Uri", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "The default Web Service endpoint uri to use for the producer." },
-    "headerFilterStrategy": { "index": 3, "kind": "parameter", "displayName": "Header Filter Strategy", "group": "common", "label": "common", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "To use a custom HeaderFilterStrategy to filter head [...]
+    "headerFilterStrategy": { "index": 3, "kind": "parameter", "displayName": "Header Filter Strategy", "group": "common", "label": "common", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "To use a custom HeaderFilterStrategy to filter head [...]
     "messageFilter": { "index": 4, "kind": "parameter", "displayName": "Message Filter", "group": "common", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.spring.ws.filter.MessageFilter", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "Option to provide a custom MessageFilter. For example wh [...]
     "messageIdStrategy": { "index": 5, "kind": "parameter", "displayName": "Message Id Strategy", "group": "common", "label": "common", "required": false, "type": "object", "javaType": "org.springframework.ws.soap.addressing.messageid.MessageIdStrategy", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "Option to provide a custom Messa [...]
     "endpointDispatcher": { "index": 6, "kind": "parameter", "displayName": "Endpoint Dispatcher", "group": "consumer", "label": "consumer", "required": false, "type": "object", "javaType": "org.apache.camel.component.spring.ws.bean.CamelEndpointDispatcher", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.spring.ws.SpringWebserviceConfiguration", "configurationField": "configuration", "description": "Spring org.springframework.w [...]
diff --git a/components/camel-spring-parent/camel-spring-ws/src/main/docs/spring-ws-component.adoc b/components/camel-spring-parent/camel-spring-ws/src/main/docs/spring-ws-component.adoc
index 2f83dc5c50a3..009ccd11adba 100644
--- a/components/camel-spring-parent/camel-spring-ws/src/main/docs/spring-ws-component.adoc
+++ b/components/camel-spring-parent/camel-spring-ws/src/main/docs/spring-ws-component.adoc
@@ -335,6 +335,17 @@ protected void doProcessSoapAttachements(Message inOrOut, SoapMessage response)
 }
 ----
 
+The `BasicMessageFilter` that the component creates for an endpoint
+applies the endpoint's `headerFilterStrategy` to the message headers it
+writes into the SOAP header, so by default headers whose names start
+with `Camel` or `camel` are not written. A message filter you provide
+yourself, global or local, does not know which endpoint it runs for: a
+`BasicMessageFilter`, or a subclass of it, created with the no-argument
+constructor applies a `SpringWebserviceHeaderFilterStrategy`, whatever
+`headerFilterStrategy` the endpoint is configured with. To apply another
+strategy, create it with the `BasicMessageFilter(Supplier<HeaderFilterStrategy>)`
+constructor, or override `getHeaderFilterStrategy()`.
+
 === Using a custom MessageSender and MessageFactory
 
 A custom message sender or factory in the registry can be referenced
diff --git a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceComponent.java b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceComponent.java
index 60a8b7dc55a5..f1e35ccd2f0c 100644
--- a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceComponent.java
+++ b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceComponent.java
@@ -168,8 +168,8 @@ public class SpringWebserviceComponent extends DefaultComponent implements SSLCo
             if (globalMessageFilter != null) {
                 configuration.setMessageFilter(globalMessageFilter);
             } else {
-                // use basic as fallback
-                configuration.setMessageFilter(new BasicMessageFilter());
+                // use basic as fallback, filtering the headers it writes with the endpoint's header filter strategy
+                configuration.setMessageFilter(new BasicMessageFilter(configuration::getHeaderFilterStrategy));
             }
         }
     }
diff --git a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceConfiguration.java b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceConfiguration.java
index 123cf22b74a4..0891c04b7d8a 100644
--- a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceConfiguration.java
+++ b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceConfiguration.java
@@ -431,7 +431,7 @@ public class SpringWebserviceConfiguration {
     /**
      * To use a custom HeaderFilterStrategy to filter headers mapped to and from the Camel message. By default the
      * internal {@code Camel} and {@code camel} header namespace (case-insensitive) is filtered out from inbound SOAP
-     * headers.
+     * headers, and the default message filter does not write it into outbound SOAP headers.
      */
     public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) {
         this.headerFilterStrategy = headerFilterStrategy;
diff --git a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/filter/impl/BasicMessageFilter.java b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/filter/impl/BasicMessageFilter.java
index 9539a26987d7..77a4d327c58a 100644
--- a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/filter/impl/BasicMessageFilter.java
+++ b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/filter/impl/BasicMessageFilter.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.spring.ws.filter.impl;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Supplier;
 
 import jakarta.activation.DataHandler;
 
@@ -27,16 +28,50 @@ import javax.xml.namespace.QName;
 import org.apache.camel.Exchange;
 import org.apache.camel.attachment.AttachmentMessage;
 import org.apache.camel.component.spring.ws.SpringWebserviceConstants;
+import org.apache.camel.component.spring.ws.SpringWebserviceHeaderFilterStrategy;
 import org.apache.camel.component.spring.ws.filter.MessageFilter;
+import org.apache.camel.spi.HeaderFilterStrategy;
 import org.springframework.ws.WebServiceMessage;
 import org.springframework.ws.soap.SoapHeader;
 import org.springframework.ws.soap.SoapMessage;
 
 /**
  * This class populates a SOAP header and attachments in the WebServiceMessage instance.
+ * <p>
+ * A message header is written into the SOAP header only when the {@link HeaderFilterStrategy} does not filter it, so by
+ * default the internal {@code Camel} and {@code camel} header namespace is not written.
  */
 public class BasicMessageFilter implements MessageFilter {
 
+    private final Supplier<HeaderFilterStrategy> headerFilterStrategy;
+
+    /**
+     * Creates a filter that applies a {@link SpringWebserviceHeaderFilterStrategy} to the message headers it writes
+     * into the SOAP header.
+     */
+    public BasicMessageFilter() {
+        HeaderFilterStrategy strategy = new SpringWebserviceHeaderFilterStrategy();
+        this.headerFilterStrategy = () -> strategy;
+    }
+
+    /**
+     * Creates a filter that applies the {@link HeaderFilterStrategy} returned by the given supplier to the message
+     * headers it writes into the SOAP header. A {@code null} strategy disables the filtering.
+     *
+     * @param headerFilterStrategy supplies the strategy to apply, such as the one configured on the endpoint
+     */
+    public BasicMessageFilter(Supplier<HeaderFilterStrategy> headerFilterStrategy) {
+        this.headerFilterStrategy = headerFilterStrategy;
+    }
+
+    /**
+     * The {@link HeaderFilterStrategy} applied to the message headers written into the SOAP header, or {@code null}
+     * when they are not filtered.
+     */
+    protected HeaderFilterStrategy getHeaderFilterStrategy() {
+        return headerFilterStrategy != null ? headerFilterStrategy.get() : null;
+    }
+
     /**
      * Whether a header is valid
      */
@@ -85,10 +120,13 @@ public class BasicMessageFilter implements MessageFilter {
      * The SOAP header is populated from exchange.getOut().getHeaders() if this class is used by the consumer or
      * exchange.getIn().getHeaders() if this class is used by the producer. If .getHeaders() contains under a certain
      * key a value with the QName object, it is directly added as a new header element. If it contains only a String
-     * value, it is transformed into a header attribute. Following headers are excluded:
+     * value, it is transformed into a header attribute. The spring-ws headers, the breadcrumb id and Content-Type are
+     * excluded, and so is any header that the {@link HeaderFilterStrategy} filters.
      */
     protected void doProcessSoapHeader(AttachmentMessage inOrOut, SoapMessage soapMessage) {
         SoapHeader soapHeader = soapMessage.getSoapHeader();
+        HeaderFilterStrategy strategy = getHeaderFilterStrategy();
+        Exchange exchange = inOrOut.getExchange();
 
         Map<String, Object> headers = inOrOut.getHeaders();
 
@@ -116,6 +154,9 @@ public class BasicMessageFilter implements MessageFilter {
             }
 
             Object value = headers.get(name);
+            if (strategy != null && strategy.applyFilterToCamelHeaders(name, value, exchange)) {
+                continue;
+            }
             if (value instanceof QName qname) {
                 soapHeader.addHeaderElement(qname);
             } else {
diff --git a/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/OutboundSoapHeaderFilterTest.java b/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/OutboundSoapHeaderFilterTest.java
new file mode 100644
index 000000000000..083880d5bada
--- /dev/null
+++ b/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/OutboundSoapHeaderFilterTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.component.spring.ws;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+import javax.xml.namespace.QName;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.camel.EndpointInject;
+import org.apache.camel.Exchange;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.spring.junit6.CamelSpringTestSupport;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.support.AbstractXmlApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.ws.client.core.WebServiceTemplate;
+import org.springframework.ws.soap.SoapHeader;
+import org.springframework.ws.soap.SoapMessage;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class OutboundSoapHeaderFilterTest extends CamelSpringTestSupport {
+
+    private final String xmlRequest = "<GetQuote xmlns=\"http://www.stockquotes.edu/\"><symbol>GOOG</symbol></GetQuote>";
+
+    @EndpointInject("mock:received")
+    private MockEndpoint received;
+
+    @Test
+    void internalCamelHeadersAreNotWrittenIntoTheConsumerResponse() {
+        List<String> attributes = responseSoapHeaderAttributes("http://www.stockquotes.edu/Respond");
+
+        assertTrue(attributes.contains("TransactionId"),
+                "Application-level message header should be written into the SOAP response header");
+        assertFalse(attributes.stream().anyMatch(name -> name.toLowerCase(Locale.ROOT).startsWith("camel")),
+                "Internal Camel* headers must not be written into the SOAP response header: " + attributes);
+    }
+
+    @Test
+    void internalCamelHeadersAreNotWrittenIntoTheProducerRequest() throws Exception {
+        received.expectedMessageCount(1);
+
+        template.send("direct:send", exchange -> {
+            exchange.getIn().setBody(xmlRequest);
+            exchange.getIn().setHeader("CamelHttpUri", "http://localhost/backend");
+            exchange.getIn().setHeader("cAmElFileName", "request.xml");
+            exchange.getIn().setHeader("TransactionId", "42");
+        });
+
+        received.assertIsSatisfied();
+        Exchange exchange = received.getExchanges().get(0);
+        String soapHeader = exchange.getIn().getHeader(SpringWebserviceConstants.SPRING_WS_SOAP_HEADER, String.class);
+        assertTrue(soapHeader.contains("TransactionId"),
+                "Application-level message header should be written into the SOAP request header");
+        assertFalse(soapHeader.toLowerCase(Locale.ROOT).contains("camelhttpuri"),
+                "Internal Camel* headers must not be written into the SOAP request header: " + soapHeader);
+        assertFalse(soapHeader.toLowerCase(Locale.ROOT).contains("camelfilename"),
+                "Internal Camel* headers must not be written into the SOAP request header: " + soapHeader);
+    }
+
+    @Test
+    void endpointHeaderFilterStrategyIsApplied() {
+        List<String> attributes = responseSoapHeaderAttributes("http://www.stockquotes.edu/RespondPassThrough");
+
+        // the endpoint's own strategy decides, so a strategy that filters nothing writes every header
+        assertTrue(attributes.contains("CamelHttpUri"),
+                "The headerFilterStrategy configured on the endpoint should be applied: " + attributes);
+    }
+
+    private List<String> responseSoapHeaderAttributes(String soapAction) {
+        WebServiceTemplate client = applicationContext.getBean("webServiceTemplate", WebServiceTemplate.class);
+        return client.sendAndReceive(
+                request -> {
+                    try {
+                        TransformerFactory.newInstance().newTransformer()
+                                .transform(new StreamSource(new StringReader(xmlRequest)), request.getPayloadResult());
+                    } catch (TransformerException e) {
+                        throw new IllegalStateException(e);
+                    }
+                    ((SoapMessage) request).setSoapAction(soapAction);
+                },
+                response -> {
+                    List<String> names = new ArrayList<>();
+                    SoapHeader soapHeader = ((SoapMessage) response).getSoapHeader();
+                    Iterator<QName> attributes = soapHeader.getAllAttributes();
+                    while (attributes.hasNext()) {
+                        names.add(attributes.next().getLocalPart());
+                    }
+                    return names;
+                });
+    }
+
+    @Override
+    protected AbstractXmlApplicationContext createApplicationContext() {
+        return new ClassPathXmlApplicationContext(
+                "org/apache/camel/component/spring/ws/OutboundSoapHeaderFilterTest-context.xml");
+    }
+}
diff --git a/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/filter/impl/BasicMessageFilterTest.java b/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/filter/impl/BasicMessageFilterTest.java
index 323ecc935044..ca0809fadc7b 100644
--- a/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/filter/impl/BasicMessageFilterTest.java
+++ b/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/filter/impl/BasicMessageFilterTest.java
@@ -16,12 +16,16 @@
  */
 package org.apache.camel.component.spring.ws.filter.impl;
 
+import java.util.List;
+import java.util.Locale;
+
 import jakarta.activation.DataHandler;
 
 import javax.xml.namespace.QName;
 
 import org.apache.camel.attachment.AttachmentMessage;
 import org.apache.camel.component.spring.ws.SpringWebserviceConstants;
+import org.apache.camel.support.DefaultHeaderFilterStrategy;
 import org.apache.camel.test.junit6.ExchangeTestSupport;
 import org.assertj.core.util.Streams;
 import org.junit.jupiter.api.BeforeEach;
@@ -184,4 +188,58 @@ class BasicMessageFilterTest extends ExchangeTestSupport {
         assertThat(Streams.stream(message.getAttachments()).toList()).isNotEmpty();
         assertThat(message.getAttachment("testAttachment")).isNotNull();
     }
+
+    @Test
+    void producerDoesNotWriteInternalCamelHeaders() {
+        exchange.getIn().setHeader("CamelHttpUri", "http://localhost/backend");
+        exchange.getIn().setHeader("cAmElFileName", "request.xml");
+        exchange.getIn().setHeader("TransactionId", "42");
+
+        filter.filterProducer(exchange, message);
+
+        assertThat(attributeNames()).contains("foo", "TransactionId")
+                .noneMatch(name -> name.toLowerCase(Locale.ROOT).startsWith("camel"));
+    }
+
+    @Test
+    void consumerDoesNotWriteInternalCamelHeaders() {
+        exchange.getMessage().setHeader("CamelHttpUri", "http://localhost/backend");
+        exchange.getMessage().setHeader("cAmElFileName", "response.xml");
+        exchange.getMessage().setHeader("TransactionId", "42");
+        exchange.getMessage().setHeader("CamelElement", new QName("http://example.com/test", "camelElement"));
+        exchange.getMessage().setHeader("AppElement", new QName("http://example.com/test", "appElement"));
+
+        filter.filterConsumer(exchange, message);
+
+        assertThat(attributeNames()).contains("foo", "TransactionId")
+                .noneMatch(name -> name.toLowerCase(Locale.ROOT).startsWith("camel"));
+        assertThat(Streams.stream(message.getSoapHeader().examineAllHeaderElements())
+                .map(element -> element.getName().getLocalPart()).toList()).containsExactly("appElement");
+    }
+
+    @Test
+    void suppliedHeaderFilterStrategyIsApplied() {
+        DefaultHeaderFilterStrategy passThrough = new DefaultHeaderFilterStrategy();
+        passThrough.setOutFilterStartsWith((String[]) null);
+        filter = new BasicMessageFilter(() -> passThrough);
+        exchange.getIn().setHeader("CamelHttpUri", "http://localhost/backend");
+
+        filter.filterProducer(exchange, message);
+
+        assertThat(attributeNames()).contains("foo", "CamelHttpUri");
+    }
+
+    @Test
+    void noHeaderFilterStrategyWritesEveryHeader() {
+        filter = new BasicMessageFilter(() -> null);
+        exchange.getIn().setHeader("CamelHttpUri", "http://localhost/backend");
+
+        filter.filterProducer(exchange, message);
+
+        assertThat(attributeNames()).contains("foo", "CamelHttpUri");
+    }
+
+    private List<String> attributeNames() {
+        return Streams.stream(message.getSoapHeader().getAllAttributes()).map(QName::getLocalPart).toList();
+    }
 }
diff --git a/components/camel-spring-parent/camel-spring-ws/src/test/resources/org/apache/camel/component/spring/ws/OutboundSoapHeaderFilterTest-context.xml b/components/camel-spring-parent/camel-spring-ws/src/test/resources/org/apache/camel/component/spring/ws/OutboundSoapHeaderFilterTest-context.xml
new file mode 100644
index 000000000000..80ea8a8db100
--- /dev/null
+++ b/components/camel-spring-parent/camel-spring-ws/src/test/resources/org/apache/camel/component/spring/ws/OutboundSoapHeaderFilterTest-context.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+         http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
+
+    <camelContext xmlns="http://camel.apache.org/schema/spring">
+        <!-- producer route (web service client) writing the message headers into the SOAP request header -->
+        <route>
+            <from uri="direct:send"/>
+            <to uri="spring-ws:http://localhost?webServiceTemplate=#webServiceTemplate&amp;soapAction=http://www.stockquotes.edu/Capture"/>
+        </route>
+
+        <!-- consumer route (the web service invoked by the producer) capturing the SOAP request it received -->
+        <route>
+            <from uri="spring-ws:soapaction:http://www.stockquotes.edu/Capture?endpointMapping=#endpointMapping"/>
+            <to uri="mock:received"/>
+            <setBody>
+                <constant>&lt;GetQuoteResponse xmlns="http://www.stockquotes.edu/"&gt;&lt;GetQuoteResult&gt;100&lt;/GetQuoteResult&gt;&lt;/GetQuoteResponse&gt;</constant>
+            </setBody>
+        </route>
+
+        <!-- consumer routes whose response message carries internal Camel headers and an application header -->
+        <route>
+            <from uri="spring-ws:soapaction:http://www.stockquotes.edu/Respond?endpointMapping=#endpointMapping"/>
+            <setHeader name="CamelHttpUri"><constant>http://localhost/backend</constant></setHeader>
+            <setHeader name="cAmElFileName"><constant>response.xml</constant></setHeader>
+            <setHeader name="TransactionId"><constant>42</constant></setHeader>
+            <setBody>
+                <constant>&lt;GetQuoteResponse xmlns="http://www.stockquotes.edu/"&gt;&lt;GetQuoteResult&gt;100&lt;/GetQuoteResult&gt;&lt;/GetQuoteResponse&gt;</constant>
+            </setBody>
+        </route>
+        <route>
+            <from uri="spring-ws:soapaction:http://www.stockquotes.edu/RespondPassThrough?endpointMapping=#endpointMapping&amp;headerFilterStrategy=#passThroughHeaderFilterStrategy"/>
+            <setHeader name="CamelHttpUri"><constant>http://localhost/backend</constant></setHeader>
+            <setHeader name="TransactionId"><constant>42</constant></setHeader>
+            <setBody>
+                <constant>&lt;GetQuoteResponse xmlns="http://www.stockquotes.edu/"&gt;&lt;GetQuoteResult&gt;100&lt;/GetQuoteResult&gt;&lt;/GetQuoteResponse&gt;</constant>
+            </setBody>
+        </route>
+    </camelContext>
+
+    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
+
+    <bean id="endpointMapping"
+          class="org.apache.camel.component.spring.ws.bean.CamelEndpointMapping"/>
+
+    <!-- a header filter strategy that does not filter any header -->
+    <bean id="passThroughHeaderFilterStrategy" class="org.apache.camel.support.DefaultHeaderFilterStrategy">
+        <property name="inFilterStartsWith">
+            <null/>
+        </property>
+        <property name="outFilterStartsWith">
+            <null/>
+        </property>
+    </bean>
+
+    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
+        <property name="defaultUri" value="http://localhost"/>
+        <property name="messageSender">
+            <bean class="net.javacrumbs.springws.test.helper.InMemoryWebServiceMessageSender2"/>
+        </property>
+    </bean>
+
+</beans>
diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpringWebserviceEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpringWebserviceEndpointBuilderFactory.java
index 929526364422..98d635cfb38f 100644
--- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpringWebserviceEndpointBuilderFactory.java
+++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpringWebserviceEndpointBuilderFactory.java
@@ -47,7 +47,8 @@ public interface SpringWebserviceEndpointBuilderFactory {
          * To use a custom HeaderFilterStrategy to filter headers mapped to and
          * from the Camel message. By default the internal Camel and camel
          * header namespace (case-insensitive) is filtered out from inbound SOAP
-         * headers.
+         * headers, and the default message filter does not write it into
+         * outbound SOAP headers.
          * 
          * The option is a:
          * <code>org.apache.camel.spi.HeaderFilterStrategy</code> type.
@@ -65,7 +66,8 @@ public interface SpringWebserviceEndpointBuilderFactory {
          * To use a custom HeaderFilterStrategy to filter headers mapped to and
          * from the Camel message. By default the internal Camel and camel
          * header namespace (case-insensitive) is filtered out from inbound SOAP
-         * headers.
+         * headers, and the default message filter does not write it into
+         * outbound SOAP headers.
          * 
          * The option will be converted to a
          * <code>org.apache.camel.spi.HeaderFilterStrategy</code> type.
@@ -410,7 +412,8 @@ public interface SpringWebserviceEndpointBuilderFactory {
          * To use a custom HeaderFilterStrategy to filter headers mapped to and
          * from the Camel message. By default the internal Camel and camel
          * header namespace (case-insensitive) is filtered out from inbound SOAP
-         * headers.
+         * headers, and the default message filter does not write it into
+         * outbound SOAP headers.
          * 
          * The option is a:
          * <code>org.apache.camel.spi.HeaderFilterStrategy</code> type.
@@ -428,7 +431,8 @@ public interface SpringWebserviceEndpointBuilderFactory {
          * To use a custom HeaderFilterStrategy to filter headers mapped to and
          * from the Camel message. By default the internal Camel and camel
          * header namespace (case-insensitive) is filtered out from inbound SOAP
-         * headers.
+         * headers, and the default message filter does not write it into
+         * outbound SOAP headers.
          * 
          * The option will be converted to a
          * <code>org.apache.camel.spi.HeaderFilterStrategy</code> type.
@@ -1026,7 +1030,8 @@ public interface SpringWebserviceEndpointBuilderFactory {
          * To use a custom HeaderFilterStrategy to filter headers mapped to and
          * from the Camel message. By default the internal Camel and camel
          * header namespace (case-insensitive) is filtered out from inbound SOAP
-         * headers.
+         * headers, and the default message filter does not write it into
+         * outbound SOAP headers.
          * 
          * The option is a:
          * <code>org.apache.camel.spi.HeaderFilterStrategy</code> type.
@@ -1044,7 +1049,8 @@ public interface SpringWebserviceEndpointBuilderFactory {
          * To use a custom HeaderFilterStrategy to filter headers mapped to and
          * from the Camel message. By default the internal Camel and camel
          * header namespace (case-insensitive) is filtered out from inbound SOAP
-         * headers.
+         * headers, and the default message filter does not write it into
+         * outbound SOAP headers.
          * 
          * The option will be converted to a
          * <code>org.apache.camel.spi.HeaderFilterStrategy</code> type.