diff --git a/function-dependencies/pom.xml b/function-dependencies/pom.xml
index 7aef8f65..4afc5601 100644
--- a/function-dependencies/pom.xml
+++ b/function-dependencies/pom.xml
@@ -227,6 +227,11 @@
header-enricher-function
${project.version}
+
+ org.springframework.cloud.fn
+ header-filter-function
+ ${project.version}
+
org.springframework.cloud.fn
http-request-function
diff --git a/function/header-filter-function/README.adoc b/function/header-filter-function/README.adoc
new file mode 100644
index 00000000..b9c9f93e
--- /dev/null
+++ b/function/header-filter-function/README.adoc
@@ -0,0 +1,25 @@
+= Header Enricher Function
+
+This module provides a header enricher function that can be reused and composed in other applications.
+
+== Beans for injection
+
+You can import the `HeaderEnricherFunctionConfiguration` in a Spring Boot application and then inject the following bean.
+
+`headerFilterFunction`
+
+You can use `headerFilterFunction` as a qualifier when injecting.
+
+Once injected, you can use the `apply` method of the `Function` to invoke it and get the result.
+
+== Configuration Options
+
+For more information on the various options available, please see link:src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionProperties.java[HeaderFilterFunctionProperties.java]
+
+== Tests
+
+See this link:src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationTests.java[test suite] for examples of how this function is used.
+
+== Other usage
+
+See this link:../../../applications/processor/header-filter-processor/README.adoc[README] where this function is used to create a Spring Cloud Stream application.
\ No newline at end of file
diff --git a/function/header-filter-function/pom.xml b/function/header-filter-function/pom.xml
new file mode 100644
index 00000000..4c2f1324
--- /dev/null
+++ b/function/header-filter-function/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+
+
+ org.springframework.cloud.fn
+ spring-functions-parent
+ 4.0.0-SNAPSHOT
+ ../../spring-functions-parent/pom.xml
+
+
+ header-filter-function
+ header-filter-function
+ Spring Native Function for applying message filters
+
+
+
+ org.springframework.cloud.fn
+ payload-converter-function
+ ${project.version}
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
diff --git a/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionConfiguration.java b/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionConfiguration.java
new file mode 100644
index 00000000..2a5fdd2f
--- /dev/null
+++ b/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionConfiguration.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2023-2023 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.fn.header.filter;
+
+import java.util.HashSet;
+import java.util.function.Function;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.integration.IntegrationMessageHeaderAccessor;
+import org.springframework.integration.transformer.HeaderFilter;
+import org.springframework.messaging.Message;
+import org.springframework.util.StringUtils;
+
+/**
+ * Configure a function using {@link HeaderFilter}.
+ *
+ * @author Corneil du Plessis
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(HeaderFilterFunctionProperties.class)
+@ConditionalOnExpression("'${header.filter.remove}'!='' or '${header.filter.delete-all}' != ''")
+public class HeaderFilterFunctionConfiguration {
+ private final HeaderFilterFunctionProperties properties;
+ public HeaderFilterFunctionConfiguration(HeaderFilterFunctionProperties properties) {
+ this.properties = properties;
+ }
+
+ @Bean
+ public Function, Message>> headerFilterFunction() {
+ if (properties.isDeleteAll()) {
+ return (message) -> {
+ var accessor = new IntegrationMessageHeaderAccessor(message);
+ var headers = new HashSet<>(message.getHeaders().keySet());
+ headers.removeIf(accessor::isReadOnly);
+ HeaderFilter filter = new HeaderFilter(headers.toArray(new String[0]));
+ return filter.transform(message);
+ };
+ }
+ else {
+ return headerFilter()::transform;
+ }
+ }
+
+ @Bean
+ public HeaderFilter headerFilter() {
+ if (properties.getRemove() != null) {
+ String[] remove = StringUtils.tokenizeToStringArray(properties.getRemove(), ", ", true, true);
+ HeaderFilter filter = new HeaderFilter(remove);
+ if (properties.getRemove().contains("*")) {
+ filter.setPatternMatch(true);
+ }
+ return filter;
+ }
+ else {
+ return new HeaderFilter("");
+ }
+ }
+
+}
diff --git a/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionProperties.java b/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionProperties.java
new file mode 100644
index 00000000..e0a873ec
--- /dev/null
+++ b/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionProperties.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023-2023 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.fn.header.filter;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Properties for configuration of header-filter-function.
+ * @author Corneil du Plessis
+ */
+@ConfigurationProperties("header.filter")
+public class HeaderFilterFunctionProperties {
+ /**
+ * Indicates the need to remove all headers.
+ */
+ private boolean deleteAll = false;
+
+ /**
+ * Remove all headers named. A comma, space separated list of header names.
+ * The names may contain patterns.
+ */
+ private String remove;
+
+ public boolean isDeleteAll() {
+ return deleteAll;
+ }
+
+ public void setDeleteAll(boolean deleteAll) {
+ this.deleteAll = deleteAll;
+ }
+
+ public String getRemove() {
+ return remove;
+ }
+
+ public void setRemove(String remove) {
+ this.remove = remove;
+ }
+}
diff --git a/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationDeleteAllTests.java b/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationDeleteAllTests.java
new file mode 100644
index 00000000..df7debf1
--- /dev/null
+++ b/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationDeleteAllTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2023-2023 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.fn.header.filter;
+
+import java.util.Set;
+import java.util.function.Function;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(classes = {
+ HeaderFilterFunctionApplicationDeleteAllTests.HeaderFilterFunctionTestApplication.class,
+ HeaderFilterFunctionConfiguration.class
+},
+ properties = {"header.filter.delete-all=true"}
+)
+public class HeaderFilterFunctionApplicationDeleteAllTests {
+ @Autowired
+ protected Function, Message>> headerFilter;
+
+ @Test
+ public void testRemoveLeavesIdTimestampAll() {
+ // given
+ final Message> message = MessageBuilder.withPayload("hello")
+ .setHeader("foo", "bar")
+ .setHeader("bar", "foo")
+ .build();
+ Message> result = headerFilter.apply(message);
+ var headers = result.getHeaders().keySet();
+ assertThat(headers).isEqualTo(Set.of("id", "timestamp"));
+ }
+
+ @SpringBootApplication
+ static class HeaderFilterFunctionTestApplication {
+ public static void main(String[] args) throws Exception {
+ SpringApplication.main(args);
+ }
+ }
+
+}
diff --git a/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationTests.java b/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationTests.java
new file mode 100644
index 00000000..6d94aa60
--- /dev/null
+++ b/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationTests.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2023-2023 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.fn.header.filter;
+
+import java.util.Set;
+import java.util.function.Function;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(classes = {
+ HeaderFilterFunctionApplicationTests.HeaderFilterFunctionTestApplication.class,
+ HeaderFilterFunctionConfiguration.class
+},
+ properties = {"header.filter.remove=foo,bar,pf-*"}
+)
+public class HeaderFilterFunctionApplicationTests {
+ @Autowired
+ protected Function, Message>> headerFilter;
+
+ @Test
+ public void testRemoveAll() {
+ // given
+ final Message> message = MessageBuilder.withPayload("hello")
+ .setHeader("foo", "bar")
+ .setHeader("bar", "foo")
+ .build();
+ Message> result = headerFilter.apply(message);
+ var headers = HeaderUtils.getNonReadOnlyHeaders(result);
+ assertThat(headers).isEmpty();
+ }
+
+ @Test
+ public void testRemoveSome() {
+ // given
+ final Message> message = MessageBuilder.withPayload("hello")
+ .setHeader("foo", "bar")
+ .setHeader("foo-bar", "bar")
+ .setHeader("bar", "foo")
+ .build();
+ Message> result = headerFilter.apply(message);
+ var headers = HeaderUtils.getNonReadOnlyHeaders(result);
+ assertThat(headers).isEqualTo(Set.of("foo-bar"));
+ }
+
+ @Test
+ public void testRemoveSomeWithWildcard() {
+ // given
+ final Message> message = MessageBuilder.withPayload("hello")
+ .setHeader("foo", "bar")
+ .setHeader("pf-foo", "bar")
+ .setHeader("pf-bar", "bar")
+ .setHeader("pfBar", "bar")
+ .setHeader("bar", "foo")
+ .build();
+ Message> result = headerFilter.apply(message);
+ var headers = HeaderUtils.getNonReadOnlyHeaders(result);
+ assertThat(result.getHeaders().keySet()).isEqualTo(Set.of("pfBar"));
+ }
+
+ @Test
+ public void testRemoveLeavesIdTimestampAll() {
+ // given
+ final Message> message = MessageBuilder.withPayload("hello")
+ .setHeader("foo", "bar")
+ .setHeader("bar", "foo")
+ .build();
+ Message> result = headerFilter.apply(message);
+
+ assertThat(result.getHeaders().keySet()).isEqualTo(Set.of("id", "timestamp"));
+ }
+
+ @SpringBootApplication
+ static class HeaderFilterFunctionTestApplication {
+ public static void main(String[] main) {
+
+ }
+ }
+
+}
diff --git a/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderUtils.java b/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderUtils.java
new file mode 100644
index 00000000..d5ac05ce
--- /dev/null
+++ b/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023-2023 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.fn.header.filter;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.jetbrains.annotations.NotNull;
+
+import org.springframework.integration.IntegrationMessageHeaderAccessor;
+import org.springframework.messaging.Message;
+
+final public class HeaderUtils {
+ private HeaderUtils() {
+ }
+
+ @NotNull
+ public static Set getNonReadOnlyHeaders(Message> message) {
+ var headers = new HashSet<>(message.getHeaders().keySet());
+ var accessor = new IntegrationMessageHeaderAccessor(message);
+ headers.removeIf(accessor::isReadOnly);
+ return headers;
+ }
+}
diff --git a/function/header-filter-function/src/test/resources/test.properties b/function/header-filter-function/src/test/resources/test.properties
new file mode 100644
index 00000000..4f937e4c
--- /dev/null
+++ b/function/header-filter-function/src/test/resources/test.properties
@@ -0,0 +1 @@
+logging.level.root=debug
diff --git a/function/pom.xml b/function/pom.xml
index 9db3f927..1f56891f 100644
--- a/function/pom.xml
+++ b/function/pom.xml
@@ -14,6 +14,7 @@
aggregator-function
filter-function
header-enricher-function
+ header-filter-function
http-request-function
spel-function
payload-converter-function