diff --git a/spring-modulith-events/pom.xml b/spring-modulith-events/pom.xml
index bc8a357c..8ab5f661 100644
--- a/spring-modulith-events/pom.xml
+++ b/spring-modulith-events/pom.xml
@@ -26,6 +26,7 @@
spring-modulith-events-kafka
spring-modulith-events-mongodb
spring-modulith-events-neo4j
+ spring-modulith-events-messaging
diff --git a/spring-modulith-events/spring-modulith-events-messaging/pom.xml b/spring-modulith-events/spring-modulith-events-messaging/pom.xml
new file mode 100644
index 00000000..4ef7f17a
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-messaging/pom.xml
@@ -0,0 +1,80 @@
+
+
+ 4.0.0
+
+
+ org.springframework.modulith
+ spring-modulith-events
+ 1.3.0-SNAPSHOT
+
+
+ Spring Modulith - Events - Spring Messaging support
+ spring-modulith-events-messaging
+
+
+ org.springframework.modulith.events.messaging
+
+
+
+
+
+ org.springframework.modulith
+ spring-modulith-api
+ ${project.version}
+
+
+
+ org.springframework.modulith
+ spring-modulith-events-core
+ ${project.version}
+
+
+
+ org.springframework
+ spring-messaging
+
+
+
+ org.springframework.integration
+ spring-integration-core
+ test
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ true
+
+
+
+
+
+ org.springframework.modulith
+ spring-modulith-starter-jdbc
+ ${project.version}
+ test
+
+
+
+ com.h2database
+ h2
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-json
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
diff --git a/spring-modulith-events/spring-modulith-events-messaging/src/main/java/org/springframework/modulith/events/messaging/SpringMessagingEventExternalizerConfiguration.java b/spring-modulith-events/spring-modulith-events-messaging/src/main/java/org/springframework/modulith/events/messaging/SpringMessagingEventExternalizerConfiguration.java
new file mode 100644
index 00000000..9cd1dde7
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-messaging/src/main/java/org/springframework/modulith/events/messaging/SpringMessagingEventExternalizerConfiguration.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2023-2024 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.modulith.events.messaging;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.modulith.events.EventExternalizationConfiguration;
+import org.springframework.modulith.events.config.EventExternalizationAutoConfiguration;
+import org.springframework.modulith.events.support.BrokerRouting;
+import org.springframework.modulith.events.support.DelegatingEventExternalizer;
+
+/**
+ * Auto-configuration to set up a {@link DelegatingEventExternalizer} to externalize events to a Spring Messaging
+ * {@link MessageChannel message channel}.
+ *
+ * @author Josh Long
+ */
+@AutoConfiguration
+@AutoConfigureAfter(EventExternalizationAutoConfiguration.class)
+@ConditionalOnClass(MessageChannel.class)
+@ConditionalOnProperty(name = "spring.modulith.events.externalization.enabled",
+ havingValue = "true",
+ matchIfMissing = true)
+class SpringMessagingEventExternalizerConfiguration {
+
+ private static final Logger logger = LoggerFactory.getLogger(SpringMessagingEventExternalizerConfiguration.class);
+
+ public static final String MODULITH_ROUTING_HEADER = "modulithRouting";
+
+ @Bean
+ DelegatingEventExternalizer springMessagingEventExternalizer(
+ EventExternalizationConfiguration configuration,
+ BeanFactory factory) {
+
+ logger.debug("Registering domain event externalization for Spring Messaging…");
+
+ var context = new StandardEvaluationContext();
+ context.setBeanResolver(new BeanFactoryResolver(factory));
+
+ return new DelegatingEventExternalizer(configuration, (target, payload) -> {
+ var routing = BrokerRouting.of(target, context);
+ var message = MessageBuilder
+ .withPayload(payload)
+ .setHeader(MODULITH_ROUTING_HEADER, routing)
+ .build();
+ if (logger.isDebugEnabled()) {
+ logger.info("trying to find a {} with name {}", MessageChannel.class.getName(), routing.getTarget());
+ }
+ var bean = factory.getBean(routing.getTarget(), MessageChannel.class);
+ bean.send(message);
+ return CompletableFuture.completedFuture(null);
+ });
+ }
+}
diff --git a/spring-modulith-events/spring-modulith-events-messaging/src/main/java/org/springframework/modulith/events/messaging/package-info.java b/spring-modulith-events/spring-modulith-events-messaging/src/main/java/org/springframework/modulith/events/messaging/package-info.java
new file mode 100644
index 00000000..54c69e5a
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-messaging/src/main/java/org/springframework/modulith/events/messaging/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Messaging event externalization support.
+ */
+@org.springframework.lang.NonNullApi
+package org.springframework.modulith.events.messaging;
diff --git a/spring-modulith-events/spring-modulith-events-messaging/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-modulith-events/spring-modulith-events-messaging/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 00000000..162e6bc8
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-messaging/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.springframework.modulith.events.messaging.SpringMessagingEventExternalizerConfiguration
diff --git a/spring-modulith-events/spring-modulith-events-messaging/src/test/java/org/springframework/modulith/events/messaging/SpringMessagingEventPublicationIntegrationTests.java b/spring-modulith-events/spring-modulith-events-messaging/src/test/java/org/springframework/modulith/events/messaging/SpringMessagingEventPublicationIntegrationTests.java
new file mode 100644
index 00000000..8d33b897
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-messaging/src/test/java/org/springframework/modulith/events/messaging/SpringMessagingEventPublicationIntegrationTests.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2023-2024 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.modulith.events.messaging;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+
+import lombok.RequiredArgsConstructor;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.annotation.Bean;
+import org.springframework.integration.core.GenericHandler;
+import org.springframework.integration.dsl.DirectChannelSpec;
+import org.springframework.integration.dsl.IntegrationFlow;
+import org.springframework.integration.dsl.MessageChannels;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.modulith.events.CompletedEventPublications;
+import org.springframework.modulith.events.Externalized;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Integration tests for Spring Messaging-based event publication.
+ *
+ * @author Josh Long
+ */
+@SpringBootTest
+class SpringMessagingEventPublicationIntegrationTests {
+
+ private static final String CHANNEL_NAME = "target";
+
+ private static final AtomicInteger COUNTER = new AtomicInteger();
+
+ @Autowired TestPublisher publisher;
+ @Autowired CompletedEventPublications completed;
+
+ @SpringBootApplication
+ static class TestConfiguration {
+
+ @Bean
+ TestPublisher testPublisher(ApplicationEventPublisher publisher) {
+ return new TestPublisher(publisher);
+ }
+
+ @Bean
+ IntegrationFlow inboundIntegrationFlow(
+ @Qualifier(CHANNEL_NAME) MessageChannel inbound) {
+
+ return IntegrationFlow
+ .from(inbound)
+ .handle((GenericHandler) (payload, headers) -> {
+ COUNTER.incrementAndGet();
+ return null;
+ })
+ .get();
+ }
+
+ @Bean(value = CHANNEL_NAME)
+ DirectChannelSpec target() {
+ return MessageChannels.direct();
+ }
+
+ }
+
+ @Test
+ void publishesEventToSpringMessaging() throws Exception {
+ var publishes = 2;
+ for (var i = 0; i < publishes; i++) {
+ publisher.publishEvent();
+ }
+ Thread.sleep(200);
+ assertThat(COUNTER.get()).isEqualTo(publishes);
+ assertThat(completed.findAll()).hasSize(publishes);
+ }
+
+ @Externalized(CHANNEL_NAME)
+ static class TestEvent {}
+
+ @RequiredArgsConstructor
+ static class TestPublisher {
+
+ private final ApplicationEventPublisher events;
+
+ @Transactional
+ void publishEvent() {
+ events.publishEvent(new TestEvent());
+ }
+ }
+}
diff --git a/spring-modulith-events/spring-modulith-events-messaging/src/test/resources/application.properties b/spring-modulith-events/spring-modulith-events-messaging/src/test/resources/application.properties
new file mode 100644
index 00000000..79d64cb2
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-messaging/src/test/resources/application.properties
@@ -0,0 +1 @@
+spring.modulith.events.jdbc.schema-initialization.enabled=true
diff --git a/spring-modulith-events/spring-modulith-events-messaging/src/test/resources/logback.xml b/spring-modulith-events/spring-modulith-events-messaging/src/test/resources/logback.xml
new file mode 100644
index 00000000..61f74674
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-messaging/src/test/resources/logback.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/docs/antora/modules/ROOT/pages/events.adoc b/src/docs/antora/modules/ROOT/pages/events.adoc
index 1ce438c0..8838d8f0 100644
--- a/src/docs/antora/modules/ROOT/pages/events.adoc
+++ b/src/docs/antora/modules/ROOT/pages/events.adoc
@@ -368,6 +368,12 @@ When routing key is set, requires SQS queue to be configured as a FIFO queue.
|Uses Spring Cloud AWS SNS support.
The logical routing key will be used as SNS message group id.
When routing key is set, requires SNS to be configured as a FIFO topic with content based deduplication enabled.
+
+|Spring Messaging
+|`spring-modulith-events-messaging`
+|Uses Spring's core `Message` and `MessageChannel` support.
+Resolves the target `MessageChannel` by its bean name given the `target` in the `Externalized` annotation. Forwards routing information as a header - called `modulithRouting` - to be processed in whatever way by downstream components, typically in a Spring Integration `IntegrationFlow`.
+
|===
[[externalization.fundamentals]]