diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java
index 2191c1c1a34..dc56a5b977c 100644
--- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java
@@ -166,6 +166,7 @@ public abstract class DocumentConfigurationProperties extends DefaultTask {
private void integrationPrefixes(Config prefix) {
prefix.accept("spring.activemq");
+ prefix.accept("spring.amqp");
prefix.accept("spring.artemis");
prefix.accept("spring.batch");
prefix.accept("spring.integration");
diff --git a/documentation/spring-boot-docs/build.gradle b/documentation/spring-boot-docs/build.gradle
index 7f20bc5ed26..e89fb1017b0 100644
--- a/documentation/spring-boot-docs/build.gradle
+++ b/documentation/spring-boot-docs/build.gradle
@@ -94,6 +94,7 @@ dependencies {
implementation(project(path: ":loader:spring-boot-loader-tools"))
implementation(project(path: ":module:spring-boot-actuator"))
implementation(project(path: ":module:spring-boot-actuator-autoconfigure"))
+ implementation(project(path: ":module:spring-boot-amqp"))
implementation(project(path: ":module:spring-boot-cache"))
implementation(project(path: ":module:spring-boot-cache-test"))
implementation(project(path: ":module:spring-boot-data-cassandra"))
diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc
index 72414df0faa..42d3b57c37f 100644
--- a/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc
+++ b/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc
@@ -1716,13 +1716,13 @@
* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq[#features.messaging.amqp.rabbit]
* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq[#messaging.amqp.rabbit]
* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq[#messaging.amqp.rabbitmq]
-* xref:reference:messaging/amqp.adoc#messaging.amqp.receiving[#boot-features-using-amqp-receiving]
-* xref:reference:messaging/amqp.adoc#messaging.amqp.receiving[#features.messaging.amqp.receiving]
-* xref:reference:messaging/amqp.adoc#messaging.amqp.receiving[#messaging.amqp.receiving]
-* xref:reference:messaging/amqp.adoc#messaging.amqp.sending-stream[#messaging.amqp.sending-stream]
-* xref:reference:messaging/amqp.adoc#messaging.amqp.sending[#boot-features-using-amqp-sending]
-* xref:reference:messaging/amqp.adoc#messaging.amqp.sending[#features.messaging.amqp.sending]
-* xref:reference:messaging/amqp.adoc#messaging.amqp.sending[#messaging.amqp.sending]
+* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.receiving[#boot-features-using-amqp-receiving]
+* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.receiving[#features.messaging.amqp.receiving]
+* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.receiving[#messaging.amqp.receiving]
+* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.sending-stream[#messaging.amqp.sending-stream]
+* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.sending[#boot-features-using-amqp-sending]
+* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.sending[#features.messaging.amqp.sending]
+* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.sending[#messaging.amqp.sending]
* xref:reference:messaging/amqp.adoc#messaging.amqp[#boot-features-amqp]
* xref:reference:messaging/amqp.adoc#messaging.amqp[#features.messaging.amqp]
* xref:reference:messaging/amqp.adoc#messaging.amqp[#messaging.amqp]
diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc
index 36e57ff478d..f816ca64d25 100644
--- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc
+++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc
@@ -86,6 +86,9 @@ The following service connections are currently supported:
| javadoc:org.springframework.boot.activemq.autoconfigure.ActiveMQConnectionDetails[]
| Containers named "symptoma/activemq" or "apache/activemq-classic"
+| javadoc:org.springframework.boot.amqp.autoconfigure.AmqpConnectionDetails[]
+| Containers named "rabbitmq"
+
| javadoc:org.springframework.boot.artemis.autoconfigure.ArtemisConnectionDetails[]
| Containers named "apache/activemq-artemis"
diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/amqp.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/amqp.adoc
index 3864f7ebe9e..15cf865bd86 100644
--- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/amqp.adoc
+++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/amqp.adoc
@@ -3,7 +3,53 @@
The Advanced Message Queuing Protocol (AMQP) is a platform-neutral, wire-level protocol for message-oriented middleware.
The Spring AMQP project applies core Spring concepts to the development of AMQP-based messaging solutions.
-Spring Boot offers several conveniences for working with AMQP through RabbitMQ, including the `spring-boot-starter-rabbitmq` starter.
+Spring Boot offers several conveniences for working with AMQP: generic AMQP 1.0 support is provided by the `spring-boot-starter-amqp` starter while specific RabbitMQ support is available via the `spring-boot-starter-rabbitmq` starter.
+
+
+
+[[messaging.amqp.generic]]
+== Generic AMQP 1.0 Support
+
+AMQP 1.0 is supported by several brokers and messaging services beyond RabbitMQ, including ActiveMQ, Azure Service Bus, and others.
+Spring AMQP provides {url-spring-amqp-docs}/amqp10-client.html[generic support for AMQP 1.0] via `org.springframework.amqp:spring-amqp-client` that is based on the https://github.com/apache/qpid-protonj2/blob/main/protonj2-client/README.md[Qpid ProtonJ2 Client Library].
+
+AMQP configuration is controlled by external configuration properties in `+spring.amqp.*+`.
+For example, you might declare the following in your configuration:
+
+[configprops,yaml]
+----
+spring:
+ amqp:
+ host: "localhost"
+ port: 5672
+ username: "admin"
+ password: "secret"
+----
+
+To configure javadoc:org.apache.qpid.protonj2.client.ConnectionOptions[connection options] of the auto-configured javadoc:org.springframework.amqp.client.AmqpConnectionFactory[], define a javadoc:org.springframework.boot.amqp.autoconfigure.ConnectionOptionsCustomizer[] bean.
+
+[[messaging.amqp.generic.sending]]
+=== Sending a Message
+
+Spring's javadoc:org.springframework.amqp.client.AmqpClient[] is auto-configured, and you can autowire it directly into your own beans, as shown in the following example:
+
+include-code::MyBean[]
+
+If a javadoc:org.springframework.amqp.support.converter.MessageConverter[] bean is defined, it is associated automatically with the auto-configured javadoc:org.springframework.amqp.client.AmqpClient[].
+If no such converter is defined and Jackson is available, javadoc:org.springframework.amqp.support.converter.JacksonJsonMessageConverter[] is used.
+
+Client-specific settings can be configured as follows:
+
+[configprops,yaml]
+----
+spring:
+ amqp:
+ client:
+ default-to-address: "/queues/default_queue"
+ completion-timeout: "500ms"
+----
+
+To further configure the auto-configured javadoc:org.springframework.amqp.client.AmqpClient[], define a javadoc:org.springframework.boot.amqp.autoconfigure.AmqpClientCustomizer[] bean.
@@ -49,8 +95,8 @@ TIP: See https://spring.io/blog/2010/06/14/understanding-amqp-the-protocol-used-
-[[messaging.amqp.sending]]
-== Sending a Message
+[[messaging.amqp.rabbitmq.sending]]
+=== Sending a Message
Spring's javadoc:org.springframework.amqp.core.AmqpTemplate[] and javadoc:org.springframework.amqp.core.AmqpAdmin[] are auto-configured, and you can autowire them directly into your own beans, as shown in the following example:
@@ -82,8 +128,8 @@ If there's a bean of type javadoc:org.springframework.amqp.rabbit.support.microm
-[[messaging.amqp.sending-stream]]
-== Sending a Message To A Stream
+[[messaging.amqp.rabbitmq.sending-stream]]
+=== Sending a Message To A Stream
To send a message to a particular stream, specify the name of the stream, as shown in the following example:
@@ -101,14 +147,14 @@ If you need to create more javadoc:org.springframework.rabbit.stream.producer.Ra
-[[messaging.amqp.sending-stream.ssl]]
-=== SSL
+[[messaging.amqp.rabbitmq.sending-stream.ssl]]
+==== SSL
To use SSL with RabbitMQ Streams, set configprop:spring.rabbitmq.stream.ssl.enabled[] to `true` or set configprop:spring.rabbitmq.stream.ssl.bundle[] to configure the xref:features/ssl.adoc#features.ssl.bundles[SSL bundle] to use.
-[[messaging.amqp.receiving]]
-== Receiving a Message
+[[messaging.amqp.rabbitmq.receiving]]
+=== Receiving a Message
When the Rabbit infrastructure is present, any bean can be annotated with javadoc:org.springframework.amqp.rabbit.annotation.RabbitListener[format=annotation] to create a listener endpoint.
If no javadoc:org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory[] has been defined, a default javadoc:org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory[] is automatically configured and you can switch to a direct container using the configprop:spring.rabbitmq.listener.type[] property.
diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc
index 72240022f6a..c925920a077 100644
--- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc
+++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc
@@ -122,6 +122,9 @@ The following service connection factories are provided in the `spring-boot-test
| javadoc:org.springframework.boot.activemq.autoconfigure.ActiveMQConnectionDetails[]
| Containers named "symptoma/activemq" or javadoc:org.testcontainers.activemq.ActiveMQContainer[]
+| javadoc:org.springframework.boot.amqp.autoconfigure.AmqpConnectionDetails[]
+| Containers of type javadoc:{url-testcontainers-rabbitmq-javadoc}/org.testcontainers.rabbitmq.RabbitMQContainer[]
+
| javadoc:org.springframework.boot.artemis.autoconfigure.ArtemisConnectionDetails[]
| Containers of type javadoc:org.testcontainers.activemq.ArtemisContainer[]
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/generic/sending/MyBean.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/generic/sending/MyBean.java
new file mode 100644
index 00000000000..8f7ffc09ff5
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/generic/sending/MyBean.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012-present 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.boot.docs.messaging.amqp.generic.sending;
+
+import org.springframework.amqp.client.AmqpClient;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MyBean {
+
+ private final AmqpClient amqpClient;
+
+ public MyBean(AmqpClient amqpClient) {
+ this.amqpClient = amqpClient;
+ }
+
+ // @fold:on // ...
+ public void sendMessage(String msg) {
+ this.amqpClient.to("/queues/test").body(msg).send();
+ }
+ // @fold:off
+
+}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/receiving/MyBean.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.java
similarity index 92%
rename from documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/receiving/MyBean.java
rename to documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.java
index d5fd3014a05..56064bfb94c 100644
--- a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/receiving/MyBean.java
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.docs.messaging.amqp.receiving;
+package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyBean.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyBean.java
similarity index 91%
rename from documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyBean.java
rename to documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyBean.java
index 0f0c59f87c9..671de83f0e3 100644
--- a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyBean.java
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyBean.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.docs.messaging.amqp.receiving.custom;
+package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving.custom;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyMessageConverter.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyMessageConverter.java
similarity index 93%
rename from documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyMessageConverter.java
rename to documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyMessageConverter.java
index ba8d391a53f..18169c7e7ab 100644
--- a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyMessageConverter.java
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyMessageConverter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.docs.messaging.amqp.receiving.custom;
+package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving.custom;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyRabbitConfiguration.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitConfiguration.java
similarity index 95%
rename from documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyRabbitConfiguration.java
rename to documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitConfiguration.java
index 172c9cbba0d..318428b08be 100644
--- a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyRabbitConfiguration.java
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitConfiguration.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.docs.messaging.amqp.receiving.custom;
+package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving.custom;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/sending/MyBean.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.java
similarity index 94%
rename from documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/sending/MyBean.java
rename to documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.java
index f2adb052cec..51b91d35769 100644
--- a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/sending/MyBean.java
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.docs.messaging.amqp.sending;
+package org.springframework.boot.docs.messaging.amqp.rabbitmq.sending;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/generic/sending/MyBean.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/generic/sending/MyBean.kt
new file mode 100644
index 00000000000..64c5174f417
--- /dev/null
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/generic/sending/MyBean.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012-present 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.boot.docs.messaging.amqp.generic.sending
+
+import org.springframework.amqp.client.AmqpClient
+import org.springframework.stereotype.Component
+
+@Component
+class MyBean(private val amqpClient: AmqpClient) {
+
+ // @fold:on // ...
+ fun someOtherMethod(msg: String) {
+ amqpClient.to("/queues/test").body(msg).send()
+ }
+ // @fold:off
+
+}
+
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/receiving/MyBean.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.kt
similarity index 92%
rename from documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/receiving/MyBean.kt
rename to documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.kt
index a3241ddb95b..9bac053496d 100644
--- a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/receiving/MyBean.kt
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.docs.messaging.amqp.receiving
+package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving
import org.springframework.amqp.rabbit.annotation.RabbitListener
import org.springframework.stereotype.Component
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyBean.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyBean.kt
similarity index 92%
rename from documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyBean.kt
rename to documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyBean.kt
index f92ffc01249..3b90bf3dc42 100644
--- a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyBean.kt
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyBean.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.docs.messaging.amqp.receiving.custom
+package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving.custom
import org.springframework.amqp.rabbit.annotation.RabbitListener
import org.springframework.stereotype.Component
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyMessageConverter.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyMessageConverter.kt
similarity index 92%
rename from documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyMessageConverter.kt
rename to documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyMessageConverter.kt
index 7b6265875cc..03b165448e0 100644
--- a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyMessageConverter.kt
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyMessageConverter.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.docs.messaging.amqp.receiving.custom
+package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving.custom
import org.springframework.amqp.core.Message
import org.springframework.amqp.core.MessageProperties
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyRabbitConfiguration.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitConfiguration.kt
similarity index 95%
rename from documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyRabbitConfiguration.kt
rename to documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitConfiguration.kt
index 95ca6f61003..900866bf246 100644
--- a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/receiving/custom/MyRabbitConfiguration.kt
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitConfiguration.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.docs.messaging.amqp.receiving.custom
+package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving.custom
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/sending/MyBean.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.kt
similarity index 93%
rename from documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/sending/MyBean.kt
rename to documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.kt
index e8ec5274920..6d21de9bce0 100644
--- a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/sending/MyBean.kt
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.docs.messaging.amqp.sending
+package org.springframework.boot.docs.messaging.amqp.rabbitmq.sending
import org.springframework.amqp.core.AmqpAdmin
import org.springframework.amqp.core.AmqpTemplate
diff --git a/module/spring-boot-amqp/build.gradle b/module/spring-boot-amqp/build.gradle
new file mode 100644
index 00000000000..7aebee0bdcb
--- /dev/null
+++ b/module/spring-boot-amqp/build.gradle
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012-present 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.
+ */
+
+plugins {
+ id "java-library"
+ id "org.springframework.boot.docker-test"
+ id "org.springframework.boot.auto-configuration"
+ id "org.springframework.boot.configuration-properties"
+ id "org.springframework.boot.deployed"
+ id "org.springframework.boot.optional-dependencies"
+}
+
+description = "Spring Boot support for AMQP 1.0"
+
+dependencies {
+ api(project(":core:spring-boot"))
+ api("org.apache.qpid:protonj2-client")
+ api("org.springframework:spring-messaging")
+ api("org.springframework.amqp:spring-amqp-client")
+
+ implementation(project(":module:spring-boot-transaction"))
+
+ optional(project(":core:spring-boot-autoconfigure"))
+ optional(project(":core:spring-boot-docker-compose"))
+ optional(project(":module:spring-boot-jackson"))
+ optional(project(":core:spring-boot-testcontainers"))
+ optional("org.testcontainers:testcontainers-rabbitmq")
+
+ dockerTestImplementation(project(":core:spring-boot-test"))
+ dockerTestImplementation(project(":test-support:spring-boot-docker-test-support"))
+ dockerTestImplementation(testFixtures(project(":core:spring-boot-docker-compose")))
+ dockerTestImplementation("ch.qos.logback:logback-classic")
+ dockerTestImplementation("org.testcontainers:testcontainers-junit-jupiter")
+
+
+ testImplementation(project(":core:spring-boot-test"))
+ testImplementation(project(":test-support:spring-boot-test-support"))
+
+ testRuntimeOnly("ch.qos.logback:logback-classic")
+}
+
+tasks.named("compileTestJava") {
+ options.nullability.checking = "tests"
+}
+
+tasks.named("compileDockerTestJava") {
+ options.nullability.checking = "tests"
+}
diff --git a/module/spring-boot-amqp/src/dockerTest/java/org/springframework/boot/amqp/autoconfigure/AmqpAutoConfigurationIntegrationTests.java b/module/spring-boot-amqp/src/dockerTest/java/org/springframework/boot/amqp/autoconfigure/AmqpAutoConfigurationIntegrationTests.java
new file mode 100644
index 00000000000..d08d29a5598
--- /dev/null
+++ b/module/spring-boot-amqp/src/dockerTest/java/org/springframework/boot/amqp/autoconfigure/AmqpAutoConfigurationIntegrationTests.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.autoconfigure;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.Test;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import org.springframework.amqp.client.AmqpClient;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.boot.testsupport.container.RabbitMqManagementContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests for {@link AmqpAutoConfiguration}.
+ *
+ * @author Stephane Nicoll
+ */
+@Testcontainers(disabledWithoutDocker = true)
+class AmqpAutoConfigurationIntegrationTests {
+
+ @Container
+ static final RabbitMqManagementContainer container = new RabbitMqManagementContainer();
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(AmqpAutoConfiguration.class))
+ .withPropertyValues("spring.amqp.host=" + container.getHost(), "spring.amqp.port=" + container.getAmqpPort());
+
+ @Test
+ void sendAndReceiveUsingAmqpClient() {
+ String queue = container.createRandomQueue();
+ this.contextRunner.run((context) -> {
+ AmqpClient amqpClient = context.getBean(AmqpClient.class);
+ assertThat(amqpClient.to(queue).body("Hello World").send().get(1, TimeUnit.SECONDS)).isTrue();
+ assertThat(amqpClient.from(queue).receiveAndConvert().get(1, TimeUnit.SECONDS)).isEqualTo("Hello World");
+ });
+ }
+
+ @Test
+ void sendAndReceiveUsingJson() {
+ String queue = container.createRandomQueue();
+ this.contextRunner.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class)).run((context) -> {
+ AmqpClient amqpClient = context.getBean(AmqpClient.class);
+ assertThat(amqpClient.to(queue).body(new TestMessage("hello", 42)).send().get(1, TimeUnit.SECONDS))
+ .isTrue();
+ assertThat(amqpClient.from(queue).receiveAndConvert().get(1, TimeUnit.SECONDS))
+ .isEqualTo(new TestMessage("hello", 42));
+ });
+ }
+
+ record TestMessage(String value, int counter) {
+
+ }
+
+}
diff --git a/module/spring-boot-amqp/src/dockerTest/java/org/springframework/boot/amqp/docker/compose/RabbitMqDockerComposeConnectionDetailsFactoryIntegrationTests.java b/module/spring-boot-amqp/src/dockerTest/java/org/springframework/boot/amqp/docker/compose/RabbitMqDockerComposeConnectionDetailsFactoryIntegrationTests.java
new file mode 100644
index 00000000000..04198e1e32b
--- /dev/null
+++ b/module/spring-boot-amqp/src/dockerTest/java/org/springframework/boot/amqp/docker/compose/RabbitMqDockerComposeConnectionDetailsFactoryIntegrationTests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.docker.compose;
+
+import org.springframework.boot.amqp.autoconfigure.AmqpConnectionDetails;
+import org.springframework.boot.amqp.autoconfigure.AmqpConnectionDetails.Address;
+import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest;
+import org.springframework.boot.testsupport.container.TestImage;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests for {@link RabbitMqDockerComposeConnectionDetailsFactory}.
+ *
+ * @author Stephane Nicoll
+ */
+class RabbitMqDockerComposeConnectionDetailsFactoryIntegrationTests {
+
+ @DockerComposeTest(composeFile = "rabbitmq-compose.yaml", image = TestImage.RABBITMQ)
+ void runCreatesConnectionDetails(AmqpConnectionDetails connectionDetails) {
+ assertThat(connectionDetails.getUsername()).isEqualTo("myuser");
+ assertThat(connectionDetails.getPassword()).isEqualTo("secret");
+ Address address = connectionDetails.getAddress();
+ assertThat(address.host()).isNotNull();
+ assertThat(address.port()).isGreaterThan(0);
+ }
+
+}
diff --git a/module/spring-boot-amqp/src/dockerTest/java/org/springframework/boot/amqp/testcontainers/RabbitMqContainerConnectionDetailsFactoryIntegrationTests.java b/module/spring-boot-amqp/src/dockerTest/java/org/springframework/boot/amqp/testcontainers/RabbitMqContainerConnectionDetailsFactoryIntegrationTests.java
new file mode 100644
index 00000000000..483ec6e90ad
--- /dev/null
+++ b/module/spring-boot-amqp/src/dockerTest/java/org/springframework/boot/amqp/testcontainers/RabbitMqContainerConnectionDetailsFactoryIntegrationTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.testcontainers;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.Test;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import org.springframework.amqp.client.AmqpClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.amqp.autoconfigure.AmqpAutoConfiguration;
+import org.springframework.boot.amqp.autoconfigure.AmqpConnectionDetails;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.boot.testsupport.container.RabbitMqManagementContainer;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link RabbitMqContainerConnectionDetailsFactory}.
+ *
+ * @author Stephane Nicoll
+ */
+@SpringJUnitConfig
+@Testcontainers(disabledWithoutDocker = true)
+class RabbitMqContainerConnectionDetailsFactoryIntegrationTests {
+
+ @Container
+ @ServiceConnection
+ static final RabbitMqManagementContainer container = new RabbitMqManagementContainer();
+
+ @Autowired(required = false)
+ private AmqpConnectionDetails connectionDetails;
+
+ @Autowired
+ private AmqpClient amqpClient;
+
+ @Test
+ void connectionCanBeMadeToRabbitMqContainer() throws Exception {
+ assertThat(this.connectionDetails).isNotNull();
+ container.createQueue("test");
+ this.amqpClient.to("/queues/test").body("test message").send();
+ Object message = this.amqpClient.from("/queues/test").receiveAndConvert().get(4, TimeUnit.MINUTES);
+ assertThat(message).isEqualTo("test message");
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ImportAutoConfiguration(AmqpAutoConfiguration.class)
+ static class TestConfiguration {
+
+ }
+
+}
diff --git a/module/spring-boot-amqp/src/dockerTest/resources/logback-test.xml b/module/spring-boot-amqp/src/dockerTest/resources/logback-test.xml
new file mode 100644
index 00000000000..b8a41480d7d
--- /dev/null
+++ b/module/spring-boot-amqp/src/dockerTest/resources/logback-test.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/module/spring-boot-amqp/src/dockerTest/resources/org/springframework/boot/amqp/docker/compose/rabbitmq-compose.yaml b/module/spring-boot-amqp/src/dockerTest/resources/org/springframework/boot/amqp/docker/compose/rabbitmq-compose.yaml
new file mode 100644
index 00000000000..1951fba4bb0
--- /dev/null
+++ b/module/spring-boot-amqp/src/dockerTest/resources/org/springframework/boot/amqp/docker/compose/rabbitmq-compose.yaml
@@ -0,0 +1,8 @@
+services:
+ rabbitmq:
+ image: '{imageName}'
+ environment:
+ - 'RABBITMQ_DEFAULT_USER=myuser'
+ - 'RABBITMQ_DEFAULT_PASS=secret'
+ ports:
+ - '5672'
diff --git a/module/spring-boot-amqp/src/dockerTest/resources/spring.properties b/module/spring-boot-amqp/src/dockerTest/resources/spring.properties
new file mode 100644
index 00000000000..47dff33f0bb
--- /dev/null
+++ b/module/spring-boot-amqp/src/dockerTest/resources/spring.properties
@@ -0,0 +1 @@
+spring.test.context.cache.maxSize=1
\ No newline at end of file
diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpAutoConfiguration.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpAutoConfiguration.java
new file mode 100644
index 00000000000..24babf2b488
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpAutoConfiguration.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.autoconfigure;
+
+import java.util.stream.Stream;
+
+import org.apache.qpid.protonj2.client.Client;
+import org.apache.qpid.protonj2.client.ClientOptions;
+import org.apache.qpid.protonj2.client.ConnectionOptions;
+import tools.jackson.databind.json.JsonMapper;
+
+import org.springframework.amqp.client.AmqpClient;
+import org.springframework.amqp.client.AmqpConnectionFactory;
+import org.springframework.amqp.client.SingleAmqpConnectionFactory;
+import org.springframework.amqp.support.converter.JacksonJsonMessageConverter;
+import org.springframework.amqp.support.converter.MessageConverter;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.amqp.autoconfigure.AmqpConnectionDetails.Address;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.context.properties.PropertyMapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Auto-configuration for generic AMQP 1.0 support.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1.0
+ */
+@AutoConfiguration(afterName = "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration")
+@ConditionalOnClass({ Client.class, AmqpConnectionFactory.class })
+@EnableConfigurationProperties(AmqpProperties.class)
+public final class AmqpAutoConfiguration {
+
+ @Configuration(proxyBeanMethods = false)
+ static class AmqpConnectionFactoryConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ Client protonClient() {
+ return Client.create(new ClientOptions());
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ AmqpConnectionDetails amqpConnectionDetails(AmqpProperties properties) {
+ return new PropertiesAmqpConnectionDetails(properties);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ AmqpConnectionFactory amqpConnectionFactory(AmqpConnectionDetails connectionDetails, Client protonClient,
+ ObjectProvider connectionOptionsCustomizers) {
+ ConnectionOptions connectionOptions = createConnectionOptions(connectionDetails,
+ connectionOptionsCustomizers.orderedStream());
+ return createAmqpConnectionFactory(connectionDetails, protonClient).setConnectionOptions(connectionOptions);
+ }
+
+ private SingleAmqpConnectionFactory createAmqpConnectionFactory(AmqpConnectionDetails connectionDetails,
+ Client protonClient) {
+ SingleAmqpConnectionFactory connectionFactory = new SingleAmqpConnectionFactory(protonClient);
+ PropertyMapper map = PropertyMapper.get();
+ Address address = connectionDetails.getAddress();
+ map.from(address::host).to(connectionFactory::setHost);
+ map.from(address::port).to(connectionFactory::setPort);
+ return connectionFactory;
+ }
+
+ private ConnectionOptions createConnectionOptions(AmqpConnectionDetails connectionDetails,
+ Stream customizers) {
+ ConnectionOptions connectionOptions = new ConnectionOptions();
+ PropertyMapper map = PropertyMapper.get();
+ map.from(connectionDetails::getUsername).to(connectionOptions::user);
+ map.from(connectionDetails::getPassword).to(connectionOptions::password);
+ customizers.forEach((customizer) -> customizer.customize(connectionOptions));
+ return connectionOptions;
+ }
+
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnBean(JsonMapper.class)
+ @ConditionalOnSingleCandidate(JsonMapper.class)
+ static class JacksonMessageConverterConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean(MessageConverter.class)
+ JacksonJsonMessageConverter jacksonJsonMessageConverter(JsonMapper jsonMapper) {
+ return new JacksonJsonMessageConverter(jsonMapper);
+ }
+
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class AmqpClientConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ AmqpClient amqpClient(AmqpConnectionFactory amqpConnectionFactory, AmqpProperties properties,
+ ObjectProvider messageConverter, ObjectProvider customizers) {
+ AmqpClient.Builder builder = AmqpClient.builder(amqpConnectionFactory);
+ AmqpProperties.Client client = properties.getClient();
+ PropertyMapper map = PropertyMapper.get();
+ map.from(client.getDefaultToAddress()).to(builder::defaultToAddress);
+ map.from(client.getCompletionTimeout()).to(builder::completionTimeout);
+ messageConverter.ifAvailable(builder::messageConverter);
+ customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
+ return builder.build();
+ }
+
+ }
+
+}
diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpClientCustomizer.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpClientCustomizer.java
new file mode 100644
index 00000000000..b2ab3c2ed76
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpClientCustomizer.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.autoconfigure;
+
+import org.springframework.amqp.client.AmqpClient;
+
+/**
+ * Callback interface that can be used to customize the auto-configured
+ * {@link AmqpClient.Builder}.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1.0
+ */
+@FunctionalInterface
+public interface AmqpClientCustomizer {
+
+ /**
+ * Callback to customize a {@link AmqpClient.Builder} instance.
+ * @param amqpClientBuilder the client builder to customize
+ */
+ void customize(AmqpClient.Builder amqpClientBuilder);
+
+}
diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpConnectionDetails.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpConnectionDetails.java
new file mode 100644
index 00000000000..246183800ae
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpConnectionDetails.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.autoconfigure;
+
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
+
+/**
+ * Details required to establish a connection to an AMQP broker.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1.0
+ */
+public interface AmqpConnectionDetails extends ConnectionDetails {
+
+ /**
+ * Return the {@link Address} of the broker.
+ * @return the address
+ */
+ Address getAddress();
+
+ /**
+ * Login user to authenticate to the broker.
+ * @return the login user to authenticate to the broker or {@code null}
+ */
+ default @Nullable String getUsername() {
+ return null;
+ }
+
+ /**
+ * Password used to authenticate to the broker.
+ * @return the password to authenticate to the broker or {@code null}
+ */
+ default @Nullable String getPassword() {
+ return null;
+ }
+
+ /**
+ * An AMQP broker address.
+ *
+ * @param host the host
+ * @param port the port
+ */
+ record Address(String host, int port) {
+ }
+
+}
diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpProperties.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpProperties.java
new file mode 100644
index 00000000000..7091fc29225
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpProperties.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.autoconfigure;
+
+import java.time.Duration;
+
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Configuration properties for AMQP 1.0 connectivity.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1.0
+ */
+@ConfigurationProperties("spring.amqp")
+public class AmqpProperties {
+
+ /**
+ * AMQP broker host.
+ */
+ private String host = "localhost";
+
+ /**
+ * AMQP broker port.
+ */
+ private int port = 5672;
+
+ /**
+ * Login user to authenticate to the broker.
+ */
+ private @Nullable String username;
+
+ /**
+ * Password used to authenticate to the broker.
+ */
+ private @Nullable String password;
+
+ private final Client client = new Client();
+
+ public String getHost() {
+ return this.host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public int getPort() {
+ return this.port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public @Nullable String getUsername() {
+ return this.username;
+ }
+
+ public void setUsername(@Nullable String username) {
+ this.username = username;
+ }
+
+ public @Nullable String getPassword() {
+ return this.password;
+ }
+
+ public void setPassword(@Nullable String password) {
+ this.password = password;
+ }
+
+ public Client getClient() {
+ return this.client;
+ }
+
+ /**
+ * Client-level settings.
+ */
+ public static class Client {
+
+ /**
+ * Default destination address for send operations when none is specified.
+ */
+ private @Nullable String defaultToAddress;
+
+ /**
+ * Maximum time to wait for request operations to complete.
+ */
+ private Duration completionTimeout = Duration.ofSeconds(60);
+
+ public @Nullable String getDefaultToAddress() {
+ return this.defaultToAddress;
+ }
+
+ public void setDefaultToAddress(@Nullable String defaultToAddress) {
+ this.defaultToAddress = defaultToAddress;
+ }
+
+ public Duration getCompletionTimeout() {
+ return this.completionTimeout;
+ }
+
+ public void setCompletionTimeout(Duration completionTimeout) {
+ this.completionTimeout = completionTimeout;
+ }
+
+ }
+
+}
diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/ConnectionOptionsCustomizer.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/ConnectionOptionsCustomizer.java
new file mode 100644
index 00000000000..9d3c54f1cdb
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/ConnectionOptionsCustomizer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.autoconfigure;
+
+import org.apache.qpid.protonj2.client.ConnectionOptions;
+
+import org.springframework.amqp.client.AmqpConnectionFactory;
+
+/**
+ * Callback interface for customizing {@link ConnectionOptions} on the auto-configured
+ * {@link AmqpConnectionFactory}.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1.0
+ */
+@FunctionalInterface
+public interface ConnectionOptionsCustomizer {
+
+ /**
+ * Customize the {@link ConnectionOptions}.
+ * @param connectionOptions the connection options to customize
+ */
+ void customize(ConnectionOptions connectionOptions);
+
+}
diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/PropertiesAmqpConnectionDetails.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/PropertiesAmqpConnectionDetails.java
new file mode 100644
index 00000000000..13e47d047a5
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/PropertiesAmqpConnectionDetails.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.autoconfigure;
+
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Adapts {@link AmqpProperties} to {@link AmqpConnectionDetails}.
+ *
+ * @author Stephane Nicoll
+ */
+class PropertiesAmqpConnectionDetails implements AmqpConnectionDetails {
+
+ private final AmqpProperties properties;
+
+ PropertiesAmqpConnectionDetails(AmqpProperties properties) {
+ this.properties = properties;
+ }
+
+ @Override
+ public Address getAddress() {
+ return new Address(this.properties.getHost(), this.properties.getPort());
+ }
+
+ @Override
+ public @Nullable String getUsername() {
+ return this.properties.getUsername();
+ }
+
+ @Override
+ public @Nullable String getPassword() {
+ return this.properties.getPassword();
+ }
+
+}
diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/package-info.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/package-info.java
new file mode 100644
index 00000000000..fef2b40e8d3
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-present 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.
+ */
+
+/**
+ * Auto-configuration for generic AMQP 1.0 support.
+ */
+@NullMarked
+package org.springframework.boot.amqp.autoconfigure;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitMqDockerComposeConnectionDetailsFactory.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitMqDockerComposeConnectionDetailsFactory.java
new file mode 100644
index 00000000000..09a45147c4f
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitMqDockerComposeConnectionDetailsFactory.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.docker.compose;
+
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.boot.amqp.autoconfigure.AmqpConnectionDetails;
+import org.springframework.boot.docker.compose.core.RunningService;
+import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory;
+import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource;
+
+/**
+ * {@link DockerComposeConnectionDetailsFactory} to create {@link AmqpConnectionDetails}
+ * for a {@code rabbitmq} service.
+ *
+ * @author Stephane Nicoll
+ */
+class RabbitMqDockerComposeConnectionDetailsFactory
+ extends DockerComposeConnectionDetailsFactory {
+
+ private static final int RABBITMQ_PORT = 5672;
+
+ protected RabbitMqDockerComposeConnectionDetailsFactory() {
+ super("rabbitmq");
+ }
+
+ @Override
+ protected @Nullable AmqpConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) {
+ try {
+ return new RabbitMqDockerComposeConnectionDetails(source.getRunningService());
+ }
+ catch (IllegalStateException ex) {
+ return null;
+ }
+ }
+
+ /**
+ * {@link AmqpConnectionDetails} backed by a {@code rabbitmq} {@link RunningService}.
+ */
+ static class RabbitMqDockerComposeConnectionDetails extends DockerComposeConnectionDetails
+ implements AmqpConnectionDetails {
+
+ private final RabbitMqEnvironment environment;
+
+ private final Address address;
+
+ protected RabbitMqDockerComposeConnectionDetails(RunningService service) {
+ super(service);
+ this.environment = new RabbitMqEnvironment(service.env());
+ this.address = new Address(service.host(), service.ports().get(RABBITMQ_PORT));
+ }
+
+ @Override
+ public Address getAddress() {
+ return this.address;
+ }
+
+ @Override
+ public @Nullable String getUsername() {
+ return this.environment.getUsername();
+ }
+
+ @Override
+ public @Nullable String getPassword() {
+ return this.environment.getPassword();
+ }
+
+ }
+
+}
diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitMqEnvironment.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitMqEnvironment.java
new file mode 100644
index 00000000000..0da210981ae
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitMqEnvironment.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.docker.compose;
+
+import java.util.Map;
+
+import org.jspecify.annotations.Nullable;
+
+/**
+ * RabbitMQ environment details.
+ *
+ * @author Moritz Halbritter
+ * @author Andy Wilkinson
+ * @author Phillip Webb
+ * @author Scott Frederick
+ */
+class RabbitMqEnvironment {
+
+ private final @Nullable String username;
+
+ private final @Nullable String password;
+
+ RabbitMqEnvironment(Map env) {
+ this.username = env.getOrDefault("RABBITMQ_DEFAULT_USER", env.getOrDefault("RABBITMQ_USERNAME", "guest"));
+ this.password = env.getOrDefault("RABBITMQ_DEFAULT_PASS", env.getOrDefault("RABBITMQ_PASSWORD", "guest"));
+ }
+
+ @Nullable String getUsername() {
+ return this.username;
+ }
+
+ @Nullable String getPassword() {
+ return this.password;
+ }
+
+}
diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/docker/compose/package-info.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/docker/compose/package-info.java
new file mode 100644
index 00000000000..6408a2ff003
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/docker/compose/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-present 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.
+ */
+
+/**
+ * Support for Docker Compose generic AMQP service connections.
+ */
+@NullMarked
+package org.springframework.boot.amqp.docker.compose;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/testcontainers/RabbitMqContainerConnectionDetailsFactory.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/testcontainers/RabbitMqContainerConnectionDetailsFactory.java
new file mode 100644
index 00000000000..a36da1c19be
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/testcontainers/RabbitMqContainerConnectionDetailsFactory.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.testcontainers;
+
+import java.net.URI;
+
+import org.testcontainers.rabbitmq.RabbitMQContainer;
+
+import org.springframework.boot.amqp.autoconfigure.AmqpConnectionDetails;
+import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory;
+import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+
+/**
+ * {@link ContainerConnectionDetailsFactory} to create {@link AmqpConnectionDetails} from
+ * a {@link ServiceConnection @ServiceConnection}-annotated {@link RabbitMQContainer}.
+ *
+ * @author Stephane Nicoll
+ */
+class RabbitMqContainerConnectionDetailsFactory
+ extends ContainerConnectionDetailsFactory {
+
+ @Override
+ protected AmqpConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) {
+ return new RabbitMqContainerConnectionDetails(source);
+ }
+
+ /**
+ * {@link AmqpConnectionDetails} for RabbitMQ backed by a
+ * {@link ContainerConnectionSource}.
+ */
+ static final class RabbitMqContainerConnectionDetails extends ContainerConnectionDetails
+ implements AmqpConnectionDetails {
+
+ private RabbitMqContainerConnectionDetails(ContainerConnectionSource source) {
+ super(source);
+ }
+
+ @Override
+ public Address getAddress() {
+ URI uri = URI.create(getContainer().getAmqpUrl());
+ return new Address(uri.getHost(), uri.getPort());
+ }
+
+ @Override
+ public String getUsername() {
+ return getContainer().getAdminUsername();
+ }
+
+ @Override
+ public String getPassword() {
+ return getContainer().getAdminPassword();
+ }
+
+ }
+
+}
diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/testcontainers/package-info.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/testcontainers/package-info.java
new file mode 100644
index 00000000000..398ec731847
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/testcontainers/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-present 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.
+ */
+
+/**
+ * Support for testcontainers generic AMQP service connections.
+ */
+@NullMarked
+package org.springframework.boot.amqp.testcontainers;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/module/spring-boot-amqp/src/main/resources/META-INF/spring.factories b/module/spring-boot-amqp/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000000..d3f0d273fc1
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,5 @@
+# Connection Details Factories
+org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\
+org.springframework.boot.amqp.docker.compose.RabbitMqDockerComposeConnectionDetailsFactory,\
+org.springframework.boot.amqp.testcontainers.RabbitMqContainerConnectionDetailsFactory
+
diff --git a/module/spring-boot-amqp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/module/spring-boot-amqp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 00000000000..1bbaad209b6
--- /dev/null
+++ b/module/spring-boot-amqp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.springframework.boot.amqp.autoconfigure.AmqpAutoConfiguration
diff --git a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/AmqpAutoConfigurationTests.java b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/AmqpAutoConfigurationTests.java
new file mode 100644
index 00000000000..42fe2fa2cef
--- /dev/null
+++ b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/AmqpAutoConfigurationTests.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.autoconfigure;
+
+import java.time.Duration;
+
+import org.apache.qpid.protonj2.client.Client;
+import org.apache.qpid.protonj2.client.ConnectionOptions;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.Test;
+import org.mockito.InOrder;
+import tools.jackson.databind.json.JsonMapper;
+
+import org.springframework.amqp.client.AmqpClient;
+import org.springframework.amqp.client.AmqpConnectionFactory;
+import org.springframework.amqp.support.converter.JacksonJsonMessageConverter;
+import org.springframework.amqp.support.converter.MessageConverter;
+import org.springframework.boot.amqp.autoconfigure.AmqpConnectionDetails.Address;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link AmqpAutoConfiguration}.
+ *
+ * @author Stephane Nicoll
+ */
+class AmqpAutoConfigurationTests {
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(AmqpAutoConfiguration.class));
+
+ @Test
+ void autoConfigurationConfiguresConnectionFactoryWithDefaultSettings() {
+ this.contextRunner.run((context) -> {
+ AmqpConnectionFactory connectionFactory = context.getBean(AmqpConnectionFactory.class);
+ assertThat(connectionFactory).hasFieldOrPropertyWithValue("host", "localhost");
+ assertThat(connectionFactory).hasFieldOrPropertyWithValue("port", 5672);
+ assertThat(connectionFactory)
+ .extracting("connectionOptions", InstanceOfAssertFactories.type(ConnectionOptions.class))
+ .satisfies((connectOptions) -> {
+ assertThat(connectOptions).hasFieldOrPropertyWithValue("user", null);
+ assertThat(connectOptions).hasFieldOrPropertyWithValue("password", null);
+ });
+ });
+ }
+
+ @Test
+ void autoConfigurationConfiguresConnectionFactoryWithOverrides() {
+ this.contextRunner
+ .withPropertyValues("spring.amqp.host=amqp.example.com", "spring.amqp.port=1234",
+ "spring.amqp.username=user", "spring.amqp.password=secret")
+ .run((context) -> {
+ AmqpConnectionFactory connectionFactory = context.getBean(AmqpConnectionFactory.class);
+ assertThat(connectionFactory).hasFieldOrPropertyWithValue("host", "amqp.example.com");
+ assertThat(connectionFactory).hasFieldOrPropertyWithValue("port", 1234);
+ assertThat(connectionFactory)
+ .extracting("connectionOptions", InstanceOfAssertFactories.type(ConnectionOptions.class))
+ .satisfies((connectOptions) -> {
+ assertThat(connectOptions).hasFieldOrPropertyWithValue("user", "user");
+ assertThat(connectOptions).hasFieldOrPropertyWithValue("password", "secret");
+ });
+ });
+ }
+
+ @Test
+ void autoConfigurationUsesAmqpConnectionDetailsForConnectionFactory() {
+ AmqpConnectionDetails connectionDetails = mock();
+ given(connectionDetails.getAddress()).willReturn(new Address("details.example.com", 9999));
+ given(connectionDetails.getUsername()).willReturn("from-details");
+ given(connectionDetails.getPassword()).willReturn("details-secret");
+ this.contextRunner.withBean(AmqpConnectionDetails.class, () -> connectionDetails)
+ .withPropertyValues("spring.amqp.host=ignored.example.com", "spring.amqp.port=1111",
+ "spring.amqp.username=ignored", "spring.amqp.password=ignored")
+ .run((context) -> {
+ AmqpConnectionFactory connectionFactory = context.getBean(AmqpConnectionFactory.class);
+ assertThat(connectionFactory).hasFieldOrPropertyWithValue("host", "details.example.com");
+ assertThat(connectionFactory).hasFieldOrPropertyWithValue("port", 9999);
+ assertThat(connectionFactory)
+ .extracting("connectionOptions", InstanceOfAssertFactories.type(ConnectionOptions.class))
+ .satisfies((connectOptions) -> {
+ assertThat(connectOptions).hasFieldOrPropertyWithValue("user", "from-details");
+ assertThat(connectOptions).hasFieldOrPropertyWithValue("password", "details-secret");
+ });
+ });
+ }
+
+ @Test
+ void autoConfigurationAppliesConnectionOptionsCustomizer() {
+ this.contextRunner
+ .withBean(ConnectionOptionsCustomizer.class, () -> (connectOptions) -> connectOptions.user("custom-user"))
+ .withPropertyValues("spring.amqp.username=user")
+ .run((context) -> {
+ AmqpConnectionFactory connectionFactory = context.getBean(AmqpConnectionFactory.class);
+ assertThat(connectionFactory)
+ .extracting("connectionOptions", InstanceOfAssertFactories.type(ConnectionOptions.class))
+ .satisfies((connectOptions) -> assertThat(connectOptions).hasFieldOrPropertyWithValue("user",
+ "custom-user"));
+ });
+ }
+
+ @Test
+ void autoConfigurationAppliesConnectionOptionsCustomizersInOrder() {
+ this.contextRunner.withUserConfiguration(ConnectionOptionsCustomizersConfiguration.class).run((context) -> {
+ ConnectionOptionsCustomizer first = context.getBean("first", ConnectionOptionsCustomizer.class);
+ ConnectionOptionsCustomizer second = context.getBean("second", ConnectionOptionsCustomizer.class);
+ InOrder ordered = inOrder(first, second);
+ ordered.verify(first).customize(any());
+ ordered.verify(second).customize(any());
+ });
+ }
+
+ @Test
+ void autoConfigurationBacksOffWhenUserProvidesProtonClient() {
+ Client protonClient = mock();
+ this.contextRunner.withBean(Client.class, () -> protonClient).run((context) -> {
+ assertThat(context).hasSingleBean(Client.class);
+ assertThat(context.getBean(Client.class)).isSameAs(protonClient);
+ });
+ }
+
+ @Test
+ void autoConfigurationBacksOffWhenUserProvidesAmqpConnectionDetails() {
+ AmqpConnectionDetails connectionDetails = mock();
+ given(connectionDetails.getAddress()).willReturn(new Address("details.example.com", 9999));
+ this.contextRunner.withBean(AmqpConnectionDetails.class, () -> connectionDetails).run((context) -> {
+ assertThat(context).hasSingleBean(AmqpConnectionDetails.class);
+ assertThat(context.getBean(AmqpConnectionDetails.class)).isSameAs(connectionDetails);
+ });
+ }
+
+ @Test
+ void autoConfigurationBacksOffWhenUserProvidesAmqpConnectionFactory() {
+ AmqpConnectionFactory amqpConnectionFactory = mock();
+ this.contextRunner.withBean(AmqpConnectionFactory.class, () -> amqpConnectionFactory).run((context) -> {
+ assertThat(context).hasSingleBean(AmqpConnectionFactory.class);
+ assertThat(context.getBean(AmqpConnectionFactory.class)).isSameAs(amqpConnectionFactory);
+ });
+ }
+
+ @Test
+ void autoConfigurationConfiguresAmqpClientWithOverrides() {
+ this.contextRunner
+ .withPropertyValues("spring.amqp.client.default-to-address=/queues/default_queue",
+ "spring.amqp.client.completion-timeout=20s")
+ .run((context) -> {
+ AmqpClient amqpClient = context.getBean(AmqpClient.class);
+ assertThat(amqpClient).hasFieldOrPropertyWithValue("defaultToAddress", "/queues/default_queue");
+ assertThat(amqpClient).hasFieldOrPropertyWithValue("completionTimeout", Duration.ofSeconds(20));
+ });
+ }
+
+ @Test
+ void autoConfigurationConfiguresAmqpClientWithJacksonMessageConverter() {
+ this.contextRunner.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class)).run((context) -> {
+ assertThat(context).hasSingleBean(JsonMapper.class);
+ AmqpClient amqpClient = context.getBean(AmqpClient.class);
+ assertThat(amqpClient)
+ .extracting("messageConverter", InstanceOfAssertFactories.type(MessageConverter.class))
+ .satisfies((messageConverter) -> assertThat(messageConverter)
+ .isInstanceOf(JacksonJsonMessageConverter.class)
+ .hasFieldOrPropertyWithValue("objectMapper", context.getBean(JsonMapper.class)));
+ });
+ }
+
+ @Test
+ void autoConfigurationConfiguresAmqpClientWithMessageConverter() {
+ MessageConverter messageConverter = mock(MessageConverter.class);
+ this.contextRunner.withBean(MessageConverter.class, () -> messageConverter).run((context) -> {
+ AmqpClient amqpClient = context.getBean(AmqpClient.class);
+ assertThat(amqpClient).hasFieldOrPropertyWithValue("messageConverter", messageConverter);
+ });
+ }
+
+ @Test
+ void autoConfigurationBacksOffWhenUserProvidesAmqpClient() {
+ AmqpClient amqpClient = mock();
+ this.contextRunner.withBean(AmqpClient.class, () -> amqpClient).run((context) -> {
+ assertThat(context).hasSingleBean(AmqpClient.class);
+ assertThat(context.getBean(AmqpClient.class)).isSameAs(amqpClient);
+ });
+ }
+
+ @Test
+ void autoConfigurationUsesUserMessageConverterWhenDefinedAlongsideJackson() {
+ MessageConverter messageConverter = mock(MessageConverter.class);
+ this.contextRunner.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class))
+ .withBean(MessageConverter.class, () -> messageConverter)
+ .run((context) -> {
+ assertThat(context).doesNotHaveBean(JacksonJsonMessageConverter.class);
+ AmqpClient amqpClient = context.getBean(AmqpClient.class);
+ assertThat(amqpClient).hasFieldOrPropertyWithValue("messageConverter", messageConverter);
+ });
+ }
+
+ @Test
+ void autoConfigurationAppliesAmqpClientCustomizer() {
+ this.contextRunner
+ .withBean(AmqpClientCustomizer.class,
+ () -> (client) -> client.defaultToAddress("/queues/custom_default_queue"))
+ .withPropertyValues("spring.amqp.client.default-to-address=/queues/default_queue")
+ .run((context) -> {
+ AmqpClient amqpClient = context.getBean(AmqpClient.class);
+ assertThat(amqpClient).hasFieldOrPropertyWithValue("defaultToAddress", "/queues/custom_default_queue");
+ });
+ }
+
+ @Test
+ void autoConfigurationAppliesAmqpClientCustomizersInOrder() {
+ this.contextRunner.withUserConfiguration(AmqpClientCustomizersConfiguration.class).run((context) -> {
+ AmqpClientCustomizer first = context.getBean("first", AmqpClientCustomizer.class);
+ AmqpClientCustomizer second = context.getBean("second", AmqpClientCustomizer.class);
+ InOrder ordered = inOrder(first, second);
+ ordered.verify(first).customize(any());
+ ordered.verify(second).customize(any());
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class ConnectionOptionsCustomizersConfiguration {
+
+ @Bean
+ @Order(2)
+ ConnectionOptionsCustomizer second() {
+ return mock();
+ }
+
+ @Bean
+ @Order(1)
+ ConnectionOptionsCustomizer first() {
+ return mock();
+ }
+
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class AmqpClientCustomizersConfiguration {
+
+ @Bean
+ @Order(2)
+ AmqpClientCustomizer second() {
+ return mock();
+ }
+
+ @Bean
+ @Order(1)
+ AmqpClientCustomizer first() {
+ return mock();
+ }
+
+ }
+
+}
diff --git a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/docker/compose/RabbitMqEnvironmentTests.java b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/docker/compose/RabbitMqEnvironmentTests.java
new file mode 100644
index 00000000000..01decb6785c
--- /dev/null
+++ b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/docker/compose/RabbitMqEnvironmentTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012-present 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.boot.amqp.docker.compose;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link RabbitMqEnvironment}.
+ *
+ * @author Moritz Halbritter
+ * @author Andy Wilkinson
+ * @author Phillip Webb
+ * @author Scott Frederick
+ */
+class RabbitMqEnvironmentTests {
+
+ @Test
+ void getUsernameWhenNoRabbitMqDefaultUser() {
+ RabbitMqEnvironment environment = new RabbitMqEnvironment(Collections.emptyMap());
+ assertThat(environment.getUsername()).isEqualTo("guest");
+ }
+
+ @Test
+ void getUsernameWhenHasRabbitMqDefaultUser() {
+ RabbitMqEnvironment environment = new RabbitMqEnvironment(Map.of("RABBITMQ_DEFAULT_USER", "me"));
+ assertThat(environment.getUsername()).isEqualTo("me");
+ }
+
+ @Test
+ void getUsernameWhenHasRabbitMqUsername() {
+ RabbitMqEnvironment environment = new RabbitMqEnvironment(Map.of("RABBITMQ_USERNAME", "me"));
+ assertThat(environment.getUsername()).isEqualTo("me");
+ }
+
+ @Test
+ void getPasswordWhenNoRabbitMqDefaultPass() {
+ RabbitMqEnvironment environment = new RabbitMqEnvironment(Collections.emptyMap());
+ assertThat(environment.getPassword()).isEqualTo("guest");
+ }
+
+ @Test
+ void getPasswordWhenHasRabbitMqDefaultPass() {
+ RabbitMqEnvironment environment = new RabbitMqEnvironment(Map.of("RABBITMQ_DEFAULT_PASS", "secret"));
+ assertThat(environment.getPassword()).isEqualTo("secret");
+ }
+
+ @Test
+ void getPasswordWhenHasRabbitMqPassword() {
+ RabbitMqEnvironment environment = new RabbitMqEnvironment(Map.of("RABBITMQ_PASSWORD", "secret"));
+ assertThat(environment.getPassword()).isEqualTo("secret");
+ }
+
+}
diff --git a/platform/spring-boot-dependencies/build.gradle b/platform/spring-boot-dependencies/build.gradle
index 6519005485b..dd544051fa0 100644
--- a/platform/spring-boot-dependencies/build.gradle
+++ b/platform/spring-boot-dependencies/build.gradle
@@ -1870,6 +1870,19 @@ bom {
releaseNotes("https://pulsar.apache.org/release-notes/versioned/pulsar-{version}")
}
}
+ library("Qpid ProtonJ2", "1.1.0") {
+ group("org.apache.qpid") {
+ modules = [
+ "protonj2",
+ "protonj2-client"
+ ]
+ }
+ links {
+ site("https://qpid.apache.org/proton")
+ javadoc("https://qpid.apache.org/releases/qpid-protonj2-{version}/api", "org.apache.qpid.protonj2")
+ releaseNotes("https://qpid.apache.org/releases/qpid-protonj2-{version}/release-notes.html")
+ }
+ }
library("Quartz", "2.5.2") {
group("org.quartz-scheduler") {
modules = [
diff --git a/settings.gradle b/settings.gradle
index 5fc768cf572..b7ceb4956e0 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -80,6 +80,7 @@ include "core:spring-boot-testcontainers"
include "module:spring-boot-activemq"
include "module:spring-boot-actuator"
include "module:spring-boot-actuator-autoconfigure"
+include "module:spring-boot-amqp"
include "module:spring-boot-artemis"
include "module:spring-boot-autoconfigure-classic"
include "module:spring-boot-autoconfigure-classic-modules"
@@ -208,6 +209,8 @@ include "starter:spring-boot-starter-activemq"
include "starter:spring-boot-starter-activemq-test"
include "starter:spring-boot-starter-actuator"
include "starter:spring-boot-starter-actuator-test"
+include "starter:spring-boot-starter-amqp"
+include "starter:spring-boot-starter-amqp-test"
include "starter:spring-boot-starter-artemis"
include "starter:spring-boot-starter-artemis-test"
include "starter:spring-boot-starter-aspectj"
diff --git a/starter/spring-boot-starter-amqp-test/build.gradle b/starter/spring-boot-starter-amqp-test/build.gradle
new file mode 100644
index 00000000000..6991f36fc91
--- /dev/null
+++ b/starter/spring-boot-starter-amqp-test/build.gradle
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012-present 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.
+ */
+
+plugins {
+ id "org.springframework.boot.starter"
+}
+
+description = "Starter for testing generic AMQP 1.0 support"
+
+dependencies {
+ api(project(":starter:spring-boot-starter-amqp"))
+ api(project(":starter:spring-boot-starter-test"))
+}
diff --git a/starter/spring-boot-starter-amqp/build.gradle b/starter/spring-boot-starter-amqp/build.gradle
new file mode 100644
index 00000000000..39de12fb113
--- /dev/null
+++ b/starter/spring-boot-starter-amqp/build.gradle
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012-present 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.
+ */
+
+plugins {
+ id "org.springframework.boot.starter"
+}
+
+description = "Starter for generic AMQP 1.0 support"
+
+dependencies {
+ api(project(":starter:spring-boot-starter"))
+
+ api(project(":module:spring-boot-amqp"))
+}
diff --git a/test-support/spring-boot-docker-test-support/src/main/java/org/springframework/boot/testsupport/container/RabbitMqManagementContainer.java b/test-support/spring-boot-docker-test-support/src/main/java/org/springframework/boot/testsupport/container/RabbitMqManagementContainer.java
new file mode 100644
index 00000000000..baea0dcddd5
--- /dev/null
+++ b/test-support/spring-boot-docker-test-support/src/main/java/org/springframework/boot/testsupport/container/RabbitMqManagementContainer.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012-present 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.boot.testsupport.container;
+
+import java.util.UUID;
+
+import org.testcontainers.rabbitmq.RabbitMQContainer;
+
+/**
+ * A {@link RabbitMQContainer} with management plugin enabled.
+ *
+ * @author Stephane Nicoll
+ */
+public class RabbitMqManagementContainer extends RabbitMQContainer {
+
+ public RabbitMqManagementContainer() {
+ super(TestImage.RABBITMQ.toString());
+ }
+
+ /**
+ * Create a queue with the given name.
+ * @param name the name of the queue
+ * @return the AMQP target of the queue
+ */
+ public String createQueue(String name) {
+ try {
+ execInContainer("rabbitmqadmin", "queues", "declare", "--name", name);
+ return "/queues/" + name;
+ }
+ catch (Exception ex) {
+ throw new RuntimeException("Could not create queue", ex);
+ }
+ }
+
+ /**
+ * Create a queue with a random, unique, name.
+ * @return the AMQP target of the queue
+ */
+ public String createRandomQueue() {
+ return createQueue(UUID.randomUUID().toString());
+ }
+
+}
diff --git a/test-support/spring-boot-docker-test-support/src/main/java/org/springframework/boot/testsupport/container/TestImage.java b/test-support/spring-boot-docker-test-support/src/main/java/org/springframework/boot/testsupport/container/TestImage.java
index e64fc32615c..5f2da85c8b5 100644
--- a/test-support/spring-boot-docker-test-support/src/main/java/org/springframework/boot/testsupport/container/TestImage.java
+++ b/test-support/spring-boot-docker-test-support/src/main/java/org/springframework/boot/testsupport/container/TestImage.java
@@ -245,9 +245,10 @@ public enum TestImage {
.withStartupTimeout(Duration.ofMinutes(3))),
/**
- * A container image suitable for testing RabbitMQ.
+ * A container image suitable for testing RabbitMQ with support for both AMQP 0.9 and
+ * 1.0. Queues can be declared using {@code rabbitmqadmin}.
*/
- RABBITMQ("rabbitmq", "3.11-alpine", () -> RabbitMQContainer.class,
+ RABBITMQ("rabbitmq", "4.2-management", () -> RabbitMQContainer.class,
(container) -> ((RabbitMQContainer) container).withStartupTimeout(Duration.ofMinutes(4))),
/**