diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5a8f11e0aa..3db6fde556 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -28,57 +28,8 @@ updates: - org.hibernate.validator:hibernate-validator - org.apache.httpcomponents.client5:httpclient5 - org.awaitility:awaitility - - org.xerial.snappy:snappy-java - - org.lz4:lz4-java - - com.github.luben:zstd-jni - - - package-ecosystem: gradle - target-branch: 3.1.x - directory: / - schedule: - interval: weekly - day: saturday - ignore: - - dependency-name: '*' - update-types: - - version-update:semver-major - - version-update:semver-minor - open-pull-requests-limit: 10 - labels: - - 'type: dependency-upgrade' - groups: - development-dependencies: - update-types: - - patch - patterns: - - com.gradle.* - - com.github.spotbugs - - io.spring.* - - org.ajoberstar.grgit - - org.antora - - io.micrometer:micrometer-docs-generator - - com.willowtreeapps.assertk:assertk-jvm - - org.hibernate.validator:hibernate-validator - - org.apache.httpcomponents.client5:httpclient5 - - org.awaitility:awaitility - - org.xerial.snappy:snappy-java - - org.lz4:lz4-java - - com.github.luben:zstd-jni - - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - day: saturday - labels: - - 'type: task' - groups: - development-dependencies: - patterns: - - '*' - package-ecosystem: github-actions - target-branch: 3.1.x directory: / schedule: interval: weekly diff --git a/.github/workflows/announce-milestone-planning.yml b/.github/workflows/announce-milestone-planning.yml index e4e90710c9..58ba601906 100644 --- a/.github/workflows/announce-milestone-planning.yml +++ b/.github/workflows/announce-milestone-planning.yml @@ -6,6 +6,6 @@ on: jobs: announce-milestone-planning: - uses: spring-io/spring-github-workflows/.github/workflows/spring-announce-milestone-planning.yml@main + uses: spring-io/spring-github-workflows/.github/workflows/spring-announce-milestone-planning.yml@v5 secrets: SPRING_RELEASE_CHAT_WEBHOOK_URL: ${{ secrets.SPRING_RELEASE_GCHAT_WEBHOOK_URL }} \ No newline at end of file diff --git a/.github/workflows/auto-cherry-pick.yml b/.github/workflows/auto-cherry-pick.yml index ad08e22428..6ba14dde29 100644 --- a/.github/workflows/auto-cherry-pick.yml +++ b/.github/workflows/auto-cherry-pick.yml @@ -8,6 +8,6 @@ on: jobs: cherry-pick-commit: - uses: spring-io/spring-github-workflows/.github/workflows/spring-cherry-pick.yml@v3 + uses: spring-io/spring-github-workflows/.github/workflows/spring-cherry-pick.yml@v5 secrets: GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/backport-issue.yml b/.github/workflows/backport-issue.yml index 68de1977f1..71e42771d5 100644 --- a/.github/workflows/backport-issue.yml +++ b/.github/workflows/backport-issue.yml @@ -7,6 +7,6 @@ on: jobs: backport-issue: - uses: spring-io/spring-github-workflows/.github/workflows/spring-backport-issue.yml@v3 + uses: spring-io/spring-github-workflows/.github/workflows/spring-backport-issue.yml@v5 secrets: GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/ci-snapshot.yml b/.github/workflows/ci-snapshot.yml index c57e8778e7..37070bd9c2 100644 --- a/.github/workflows/ci-snapshot.yml +++ b/.github/workflows/ci-snapshot.yml @@ -17,10 +17,33 @@ concurrency: jobs: build-snapshot: - uses: spring-io/spring-github-workflows/.github/workflows/spring-artifactory-gradle-snapshot.yml@v3 - with: - gradleTasks: ${{ github.event_name == 'schedule' && '--rerun-tasks' || '' }} - secrets: - GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} - ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} - ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} \ No newline at end of file + runs-on: ubuntu-latest + name: CI Build SNAPSHOT for ${{ github.ref_name }} + env: + DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + + steps: + - name: Start RabbitMQ + uses: namoshek/rabbitmq-github-action@v1 + with: + ports: '5672:5672 15672:15672 5552:5552' + plugins: rabbitmq_stream,rabbitmq_management,rabbitmq_delayed_message_exchange,rabbitmq_consistent_hash_exchange + + - uses: actions/checkout@v4 + with: + show-progress: false + + - name: Checkout Common Repo + uses: actions/checkout@v4 + with: + repository: spring-io/spring-github-workflows + path: spring-github-workflows + show-progress: false + + - name: Build and Publish + timeout-minutes: 30 + uses: ./spring-github-workflows/.github/actions/spring-artifactory-gradle-build + with: + gradleTasks: ${{ github.event_name == 'schedule' && '--rerun-tasks' || '' }} + artifactoryUsername: ${{ secrets.ARTIFACTORY_USERNAME }} + artifactoryPassword: ${{ secrets.ARTIFACTORY_PASSWORD }} diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 5726382e2f..2065ee7187 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -16,4 +16,4 @@ permissions: jobs: dispatch-docs-build: if: github.repository_owner == 'spring-projects' - uses: spring-io/spring-github-workflows/.github/workflows/spring-dispatch-docs-build.yml@v3 + uses: spring-io/spring-github-workflows/.github/workflows/spring-dispatch-docs-build.yml@v5 diff --git a/.github/workflows/merge-dependabot-pr.yml b/.github/workflows/merge-dependabot-pr.yml index 0b1d927d8e..f513c72567 100644 --- a/.github/workflows/merge-dependabot-pr.yml +++ b/.github/workflows/merge-dependabot-pr.yml @@ -12,7 +12,7 @@ jobs: merge-dependabot-pr: permissions: write-all - uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@main + uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@v5 with: mergeArguments: --auto --squash autoMergeSnapshots: true \ No newline at end of file diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index b03b5bf93d..c9f71e8bdf 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -8,4 +8,4 @@ on: jobs: build-pull-request: - uses: spring-io/spring-github-workflows/.github/workflows/spring-gradle-pull-request-build.yml@v3 + uses: spring-io/spring-github-workflows/.github/workflows/spring-gradle-pull-request-build.yml@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c0bea04ccc..be8a9e40c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,10 +12,10 @@ jobs: contents: write issues: write - uses: spring-io/spring-github-workflows/.github/workflows/spring-artifactory-gradle-release.yml@main + uses: spring-io/spring-github-workflows/.github/workflows/spring-artifactory-gradle-release.yml@v5 secrets: GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} - GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} + DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} JF_ARTIFACTORY_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} diff --git a/.github/workflows/verify-staged-artifacts.yml b/.github/workflows/verify-staged-artifacts.yml index bee9abc943..9a83a2024d 100644 --- a/.github/workflows/verify-staged-artifacts.yml +++ b/.github/workflows/verify-staged-artifacts.yml @@ -10,9 +10,7 @@ on: env: - GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} - GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} + DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} diff --git a/build.gradle b/build.gradle index b7eb6a7dc7..5877efe080 100644 --- a/build.gradle +++ b/build.gradle @@ -18,13 +18,13 @@ buildscript { plugins { id 'base' id 'idea' - id 'org.ajoberstar.grgit' version '5.2.2' + id 'org.ajoberstar.grgit' version '5.3.0' id 'io.spring.nohttp' version '0.0.11' id 'io.spring.dependency-management' version '1.1.6' apply false id 'org.antora' version '1.0.0' id 'io.spring.antora.generate-antora-yml' version '0.0.1' - id 'com.github.spotbugs' version '6.0.21' - id 'io.freefair.aggregate-javadoc' version '8.6' + id 'com.github.spotbugs' version '6.0.26' + id 'io.freefair.aggregate-javadoc' version '8.10.2' } description = 'Spring AMQP' @@ -48,32 +48,28 @@ ext { assertjVersion = '3.26.3' assertkVersion = '0.28.1' awaitilityVersion = '4.2.2' - commonsCompressVersion = '1.26.2' - commonsHttpClientVersion = '5.3.1' + commonsHttpClientVersion = '5.4.1' commonsPoolVersion = '2.12.0' - hamcrestVersion = '2.2' - hibernateValidationVersion = '8.0.1.Final' - jacksonBomVersion = '2.17.2' + hamcrestVersion = '3.0' + hibernateValidationVersion = '8.0.2.Final' + jacksonBomVersion = '2.18.2' jaywayJsonPathVersion = '2.9.0' junit4Version = '4.13.2' - junitJupiterVersion = '5.11.0' + junitJupiterVersion = '5.11.4' kotlinCoroutinesVersion = '1.8.1' - log4jVersion = '2.23.1' - logbackVersion = '1.5.7' - lz4Version = '1.8.0' - micrometerDocsVersion = '1.0.3' - micrometerVersion = '1.14.0-SNAPSHOT' - micrometerTracingVersion = '1.4.0-SNAPSHOT' - mockitoVersion = '5.12.0' - rabbitmqStreamVersion = '0.15.0' - rabbitmqVersion = '5.21.0' - reactorVersion = '2024.0.0-SNAPSHOT' - snappyVersion = '1.1.10.6' - springDataVersion = '2024.0.3' - springRetryVersion = '2.0.8' - springVersion = '6.2.0-SNAPSHOT' - testcontainersVersion = '1.19.8' - zstdJniVersion = '1.5.6-5' + log4jVersion = '2.24.3' + logbackVersion = '1.5.12' + micrometerDocsVersion = '1.0.4' + micrometerVersion = '1.14.2' + micrometerTracingVersion = '1.4.1' + mockitoVersion = '5.14.2' + rabbitmqStreamVersion = '0.18.0' + rabbitmqVersion = '5.22.0' + reactorVersion = '2024.0.1' + springDataVersion = '2024.1.1' + springRetryVersion = '2.0.11' + springVersion = '6.2.1' + testcontainersVersion = '1.20.4' javaProjects = subprojects - project(':spring-amqp-bom') } @@ -83,11 +79,11 @@ antora { playbook = file('src/reference/antora/antora-playbook.yml') options = ['to-dir' : project.layout.buildDirectory.dir('site').get().toString(), clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true] dependencies = [ - '@antora/atlas-extension': '1.0.0-alpha.1', - '@antora/collector-extension': '1.0.0-alpha.3', - '@asciidoctor/tabs': '1.0.0-beta.3', - '@springio/antora-extensions': '1.11.1', - '@springio/asciidoctor-extensions': '1.0.0-alpha.10', + '@antora/atlas-extension': '1.0.0-alpha.2', + '@antora/collector-extension': '1.0.0-beta.3', + '@asciidoctor/tabs': '1.0.0-beta.6', + '@springio/antora-extensions': '1.14.2', + '@springio/asciidoctor-extensions': '1.0.0-alpha.14', ] } @@ -315,7 +311,7 @@ configure(javaProjects) { subproject -> checkstyle { configDirectory.set(rootProject.file("src/checkstyle")) - toolVersion = '10.8.0' + toolVersion = '10.18.2' } jar { @@ -469,13 +465,10 @@ project('spring-rabbit-stream') { testRuntimeOnly 'com.fasterxml.jackson.core:jackson-databind' testRuntimeOnly 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml' testRuntimeOnly 'com.fasterxml.jackson.module:jackson-module-kotlin' - testRuntimeOnly "org.apache.commons:commons-compress:$commonsCompressVersion" - testRuntimeOnly "org.xerial.snappy:snappy-java:$snappyVersion" - testRuntimeOnly "org.lz4:lz4-java:$lz4Version" - testRuntimeOnly "com.github.luben:zstd-jni:$zstdJniVersion" + testImplementation "org.testcontainers:rabbitmq" testImplementation "org.testcontainers:junit-jupiter" - testImplementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion" + testImplementation 'org.apache.logging.log4j:log4j-slf4j-impl' testImplementation 'org.springframework:spring-webflux' testImplementation 'io.micrometer:micrometer-observation-test' testImplementation 'io.micrometer:micrometer-tracing-bridge-brave' diff --git a/gradle.properties b/gradle.properties index 6bc0422ab1..8e33f5cdb5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=3.2.0-SNAPSHOT +version=3.2.2-SNAPSHOT org.gradle.jvmargs=-Xms512m -Xmx4g -Dfile.encoding=UTF-8 org.gradle.daemon=true org.gradle.caching=true diff --git a/settings.gradle b/settings.gradle index 4e1e2619ab..083f33a8a1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,8 +6,7 @@ pluginManagement { } plugins { - id 'com.gradle.develocity' version '3.17.6' - id 'io.spring.develocity.conventions' version '0.0.20' + id 'io.spring.develocity.conventions' version '0.0.22' } rootProject.name = 'spring-amqp-dist' diff --git a/spring-amqp/src/main/java/org/springframework/amqp/core/AbstractBuilder.java b/spring-amqp/src/main/java/org/springframework/amqp/core/AbstractBuilder.java index fe09c2b6d6..30506f62ca 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/core/AbstractBuilder.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/core/AbstractBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 the original author or authors. + * Copyright 2016-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. @@ -23,6 +23,7 @@ * Base class for builders supporting arguments. * * @author Gary Russell + * @author Ngoc Nhan * @since 1.6 * */ @@ -36,7 +37,7 @@ public abstract class AbstractBuilder { */ protected Map getOrCreateArguments() { if (this.arguments == null) { - this.arguments = new LinkedHashMap(); + this.arguments = new LinkedHashMap<>(); } return this.arguments; } diff --git a/spring-amqp/src/main/java/org/springframework/amqp/core/AbstractDeclarable.java b/spring-amqp/src/main/java/org/springframework/amqp/core/AbstractDeclarable.java index 83002a2ec3..2a18103b5e 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/core/AbstractDeclarable.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/core/AbstractDeclarable.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -25,7 +25,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; - import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -34,20 +33,21 @@ * * @author Gary Russell * @author Christian Tzolov + * @author Ngoc Nhan * @since 1.2 * */ public abstract class AbstractDeclarable implements Declarable { - private final Lock lock = new ReentrantLock(); + private final Lock lock = new ReentrantLock(); - private boolean shouldDeclare = true; + private final Map arguments; - private Collection declaringAdmins = new ArrayList(); + private boolean shouldDeclare = true; private boolean ignoreDeclarationExceptions; - private final Map arguments; + private Collection declaringAdmins = new ArrayList<>(); public AbstractDeclarable() { this(null); @@ -63,7 +63,7 @@ public AbstractDeclarable(@Nullable Map arguments) { this.arguments = new HashMap<>(arguments); } else { - this.arguments = new HashMap(); + this.arguments = new HashMap<>(); } } @@ -73,7 +73,7 @@ public boolean shouldDeclare() { } /** - * Whether or not this object should be automatically declared + * Whether this object should be automatically declared * by any {@code AmqpAdmin}. Default is {@code true}. * @param shouldDeclare true or false. */ @@ -101,14 +101,14 @@ public void setIgnoreDeclarationExceptions(boolean ignoreDeclarationExceptions) } @Override - public void setAdminsThatShouldDeclare(Object... adminArgs) { - Collection admins = new ArrayList(); + public void setAdminsThatShouldDeclare(@Nullable Object... adminArgs) { + Collection admins = new ArrayList<>(); if (adminArgs != null) { if (adminArgs.length > 1) { Assert.noNullElements(adminArgs, "'admins' cannot contain null elements"); } if (adminArgs.length > 0 && !(adminArgs.length == 1 && adminArgs[0] == null)) { - admins.addAll(Arrays.asList(adminArgs)); + admins = Arrays.asList(adminArgs); } } this.declaringAdmins = admins; diff --git a/spring-amqp/src/main/java/org/springframework/amqp/core/Address.java b/spring-amqp/src/main/java/org/springframework/amqp/core/Address.java index c1d10642fb..564c752d16 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/core/Address.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/core/Address.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-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. @@ -16,6 +16,7 @@ package org.springframework.amqp.core; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -39,6 +40,7 @@ * @author Dave Syer * @author Artem Bilan * @author Gary Russell + * @author Ngoc Nhan */ public class Address { @@ -111,21 +113,9 @@ public String getRoutingKey() { @Override public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Address address = (Address) o; - - return !(this.exchangeName != null - ? !this.exchangeName.equals(address.exchangeName) - : address.exchangeName != null) - && !(this.routingKey != null - ? !this.routingKey.equals(address.routingKey) - : address.routingKey != null); + return o instanceof Address address + && Objects.equals(this.exchangeName, address.exchangeName) + && Objects.equals(this.routingKey, address.routingKey); } @Override diff --git a/spring-amqp/src/main/java/org/springframework/amqp/core/Binding.java b/spring-amqp/src/main/java/org/springframework/amqp/core/Binding.java index a5ee74273f..ac2ac58acd 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/core/Binding.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/core/Binding.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -30,6 +30,7 @@ * @author Mark Fisher * @author Dave Syer * @author Gary Russell + * @author Ngoc Nhan * * @see AmqpAdmin */ @@ -74,7 +75,7 @@ public Binding(@Nullable Queue lazyQueue, @Nullable String destination, Destinat String exchange, @Nullable String routingKey, @Nullable Map arguments) { super(arguments); - Assert.isTrue(lazyQueue == null || destinationType.equals(DestinationType.QUEUE), + Assert.isTrue(lazyQueue == null || destinationType == DestinationType.QUEUE, "'lazyQueue' must be null for destination type " + destinationType); Assert.isTrue(lazyQueue != null || destination != null, "`destination` cannot be null"); this.lazyQueue = lazyQueue; diff --git a/spring-amqp/src/main/java/org/springframework/amqp/core/BindingBuilder.java b/spring-amqp/src/main/java/org/springframework/amqp/core/BindingBuilder.java index 642481750d..d297f16d9d 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/core/BindingBuilder.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/core/BindingBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -30,6 +30,7 @@ * @author Mark Fisher * @author Dave Syer * @author Gary Russell + * @author Ngoc Nhan */ public final class BindingBuilder { @@ -50,7 +51,7 @@ public static DestinationConfigurer bind(Exchange exchange) { } private static Map createMapForKeys(String... keys) { - Map map = new HashMap(); + Map map = new HashMap<>(); for (String key : keys) { map.put(key, null); } @@ -81,7 +82,7 @@ public static final class DestinationConfigurer { } public Binding to(FanoutExchange exchange) { - return new Binding(this.queue, this.name, this.type, exchange.getName(), "", new HashMap()); + return new Binding(this.queue, this.name, this.type, exchange.getName(), "", new HashMap<>()); } public HeadersExchangeMapConfigurer to(HeadersExchange exchange) { @@ -155,7 +156,7 @@ public Binding exists() { } public Binding matches(Object value) { - Map map = new HashMap(); + Map map = new HashMap<>(); map.put(this.key, value); return new Binding(HeadersExchangeMapConfigurer.this.destination.queue, HeadersExchangeMapConfigurer.this.destination.name, @@ -194,7 +195,7 @@ public final class HeadersExchangeMapBindingCreator { HeadersExchangeMapBindingCreator(Map headerMap, boolean matchAll) { Assert.notEmpty(headerMap, "header map must not be empty"); - this.headerMap = new HashMap(headerMap); + this.headerMap = new HashMap<>(headerMap); this.headerMap.put("x-match", (matchAll ? "all" : "any")); } @@ -230,12 +231,12 @@ public static final class TopicExchangeRoutingKeyConfigurer extends AbstractRout public Binding with(String routingKey) { return new Binding(destination.queue, destination.name, destination.type, exchange, routingKey, - Collections.emptyMap()); + Collections.emptyMap()); } public Binding with(Enum routingKeyEnum) { return new Binding(destination.queue, destination.name, destination.type, exchange, - routingKeyEnum.toString(), Collections.emptyMap()); + routingKeyEnum.toString(), Collections.emptyMap()); } } @@ -281,7 +282,7 @@ public Binding and(Map map) { public Binding noargs() { return new Binding(this.configurer.destination.queue, this.configurer.destination.name, this.configurer.destination.type, this.configurer.exchange, - this.routingKey, Collections.emptyMap()); + this.routingKey, Collections.emptyMap()); } } @@ -297,17 +298,17 @@ public static final class DirectExchangeRoutingKeyConfigurer extends AbstractRou public Binding with(String routingKey) { return new Binding(destination.queue, destination.name, destination.type, exchange, routingKey, - Collections.emptyMap()); + Collections.emptyMap()); } public Binding with(Enum routingKeyEnum) { return new Binding(destination.queue, destination.name, destination.type, exchange, - routingKeyEnum.toString(), Collections.emptyMap()); + routingKeyEnum.toString(), Collections.emptyMap()); } public Binding withQueueName() { return new Binding(destination.queue, destination.name, destination.type, exchange, destination.name, - Collections.emptyMap()); + Collections.emptyMap()); } } diff --git a/spring-amqp/src/main/java/org/springframework/amqp/core/Declarable.java b/spring-amqp/src/main/java/org/springframework/amqp/core/Declarable.java index da6d4788d9..b6948116fc 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/core/Declarable.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/core/Declarable.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-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. @@ -61,7 +61,7 @@ public interface Declarable { * the behavior such that all admins will declare the object. * @param adminArgs The admins. */ - void setAdminsThatShouldDeclare(Object... adminArgs); + void setAdminsThatShouldDeclare(@Nullable Object... adminArgs); /** * Add an argument to the declarable. diff --git a/spring-amqp/src/main/java/org/springframework/amqp/core/Message.java b/spring-amqp/src/main/java/org/springframework/amqp/core/Message.java index 3cb4872102..4039013615 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/core/Message.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/core/Message.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -36,6 +36,7 @@ * @author Gary Russell * @author Alex Panchenko * @author Artem Bilan + * @author Ngoc Nhan */ public class Message implements Serializable { @@ -168,14 +169,9 @@ public boolean equals(Object obj) { return false; } if (this.messageProperties == null) { - if (other.messageProperties != null) { - return false; - } - } - else if (!this.messageProperties.equals(other.messageProperties)) { - return false; + return other.messageProperties == null; } - return true; + return this.messageProperties.equals(other.messageProperties); } diff --git a/spring-amqp/src/main/java/org/springframework/amqp/core/MessageProperties.java b/spring-amqp/src/main/java/org/springframework/amqp/core/MessageProperties.java index efb2af4b61..81e4954d4c 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/core/MessageProperties.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/core/MessageProperties.java @@ -36,6 +36,7 @@ * @author Artem Bilan * @author Csaba Soti * @author Raylax Grey + * @author Ngoc Nhan */ public class MessageProperties implements Serializable { @@ -63,6 +64,13 @@ public class MessageProperties implements Serializable { public static final String X_DELAY = "x-delay"; + /** + * The custom header to represent a number of retries a message is republished. + * In case of server-side DLX, this header contains the value of {@code x-death.count} property. + * When republish is done manually, this header has to be incremented by the application. + */ + public static final String RETRY_COUNT = "retry-count"; + public static final String DEFAULT_CONTENT_TYPE = CONTENT_TYPE_BYTES; public static final MessageDeliveryMode DEFAULT_DELIVERY_MODE = MessageDeliveryMode.PERSISTENT; @@ -131,6 +139,8 @@ public class MessageProperties implements Serializable { private MessageDeliveryMode receivedDeliveryMode; + private long retryCount; + private boolean finalRetryForMessageWithNoId; private long publishSequenceNumber; @@ -468,6 +478,33 @@ public void setDelayLong(Long delay) { this.headers.put(X_DELAY, delay); } + /** + * The number of retries for this message over broker. + * @return the retry count + * @since 3.2 + */ + public long getRetryCount() { + return this.retryCount; + } + + /** + * Set a number of retries for this message over broker. + * @param retryCount the retry count. + * @since 3.2 + * @see #incrementRetryCount() + */ + public void setRetryCount(long retryCount) { + this.retryCount = retryCount; + } + + /** + * Increment a retry count for this message when it is re-published back to the broker. + * @since 3.2 + */ + public void incrementRetryCount() { + this.retryCount++; + } + public boolean isFinalRetryForMessageWithNoId() { return this.finalRetryForMessageWithNoId; } @@ -776,14 +813,9 @@ else if (!this.type.equals(other.type)) { return false; } if (this.userId == null) { - if (other.userId != null) { - return false; - } - } - else if (!this.userId.equals(other.userId)) { - return false; + return other.userId == null; } - return true; + return this.userId.equals(other.userId); } @Override // NOSONAR complexity diff --git a/spring-amqp/src/main/java/org/springframework/amqp/core/QueueInformation.java b/spring-amqp/src/main/java/org/springframework/amqp/core/QueueInformation.java index de914f9439..f2d8df2453 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/core/QueueInformation.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/core/QueueInformation.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 the original author or authors. + * Copyright 2019-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. @@ -20,6 +20,7 @@ * Information about a queue, resulting from a passive declaration. * * @author Gary Russell + * @author Ngoc Nhan * @since 2.2 * */ @@ -70,14 +71,9 @@ public boolean equals(Object obj) { } QueueInformation other = (QueueInformation) obj; if (this.name == null) { - if (other.name != null) { - return false; - } + return other.name == null; } - else if (!this.name.equals(other.name)) { - return false; - } - return true; + return this.name.equals(other.name); } @Override diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/AmqpHeaders.java b/spring-amqp/src/main/java/org/springframework/amqp/support/AmqpHeaders.java index a4f64b6475..5478b48573 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/AmqpHeaders.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/AmqpHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-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. @@ -138,4 +138,10 @@ public abstract class AmqpHeaders { */ public static final String BATCH_SIZE = PREFIX + "batchSize"; + /** + * The number of retries for the message over server republishing. + * @since 3.2 + */ + public static final String RETRY_COUNT = PREFIX + "retryCount"; + } diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/SimpleAmqpHeaderMapper.java b/spring-amqp/src/main/java/org/springframework/amqp/support/SimpleAmqpHeaderMapper.java index 2c9a35864b..3f3f5bffbb 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/SimpleAmqpHeaderMapper.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/SimpleAmqpHeaderMapper.java @@ -49,6 +49,7 @@ * @author Artem Bilan * @author Stephane Nicoll * @author Raylax Grey + * @author Ngoc Nhan * @since 1.4 */ public class SimpleAmqpHeaderMapper extends AbstractHeaderMapper implements AmqpHeaderMapper { @@ -96,6 +97,8 @@ public void fromHeaders(MessageHeaders headers, MessageProperties amqpMessagePro amqpMessageProperties::setTimestamp) .acceptIfNotNull(getHeaderIfAvailable(headers, AmqpHeaders.TYPE, String.class), amqpMessageProperties::setType) + .acceptIfNotNull(getHeaderIfAvailable(headers, AmqpHeaders.RETRY_COUNT, Long.class), + amqpMessageProperties::setRetryCount) .acceptIfHasText(getHeaderIfAvailable(headers, AmqpHeaders.USER_ID, String.class), amqpMessageProperties::setUserId); @@ -125,7 +128,7 @@ public void fromHeaders(MessageHeaders headers, MessageProperties amqpMessagePro @Override public MessageHeaders toHeaders(MessageProperties amqpMessageProperties) { - Map headers = new HashMap(); + Map headers = new HashMap<>(); try { BiConsumer putObject = headers::put; BiConsumer putString = headers::put; @@ -165,11 +168,10 @@ public MessageHeaders toHeaders(MessageProperties amqpMessageProperties) { .acceptIfHasText(AmqpHeaders.CONSUMER_TAG, amqpMessageProperties.getConsumerTag(), putString) .acceptIfHasText(AmqpHeaders.CONSUMER_QUEUE, amqpMessageProperties.getConsumerQueue(), putString); headers.put(AmqpHeaders.LAST_IN_BATCH, amqpMessageProperties.isLastInBatch()); + headers.put(AmqpHeaders.RETRY_COUNT, amqpMessageProperties.getRetryCount()); // Map custom headers - for (Map.Entry entry : amqpMessageProperties.getHeaders().entrySet()) { - headers.put(entry.getKey(), entry.getValue()); - } + headers.putAll(amqpMessageProperties.getHeaders()); } catch (Exception e) { if (logger.isWarnEnabled()) { diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AbstractJavaTypeMapper.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AbstractJavaTypeMapper.java index 4745f9623d..e19c62dc17 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AbstractJavaTypeMapper.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AbstractJavaTypeMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-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. @@ -35,6 +35,7 @@ * @author Sam Nelson * @author Andreas Asplund * @author Gary Russell + * @author Ngoc Nhan */ public abstract class AbstractJavaTypeMapper implements BeanClassLoaderAware { @@ -44,9 +45,9 @@ public abstract class AbstractJavaTypeMapper implements BeanClassLoaderAware { public static final String DEFAULT_KEY_CLASSID_FIELD_NAME = "__KeyTypeId__"; - private final Map> idClassMapping = new HashMap>(); + private final Map> idClassMapping = new HashMap<>(); - private final Map, String> classIdMapping = new HashMap, String>(); + private final Map, String> classIdMapping = new HashMap<>(); private ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); @@ -98,11 +99,9 @@ protected String retrieveHeader(MessageProperties properties, String headerName) protected String retrieveHeaderAsString(MessageProperties properties, String headerName) { Map headers = properties.getHeaders(); Object classIdFieldNameValue = headers.get(headerName); - String classId = null; - if (classIdFieldNameValue != null) { - classId = classIdFieldNameValue.toString(); - } - return classId; + return classIdFieldNameValue != null + ? classIdFieldNameValue.toString() + : null; } private void createReverseMap() { diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AllowedListDeserializingMessageConverter.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AllowedListDeserializingMessageConverter.java index 14d6247ddd..4f5e6d24ae 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AllowedListDeserializingMessageConverter.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/AllowedListDeserializingMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 the original author or authors. + * Copyright 2016-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. @@ -27,12 +27,13 @@ * MessageConverters that potentially use Java deserialization. * * @author Gary Russell + * @author Ngoc Nhan * @since 1.5.5 * */ public abstract class AllowedListDeserializingMessageConverter extends AbstractMessageConverter { - private final Set allowedListPatterns = new LinkedHashSet(); + private final Set allowedListPatterns = new LinkedHashSet<>(); /** * Set simple patterns for allowable packages/classes for deserialization. diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/ContentTypeDelegatingMessageConverter.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/ContentTypeDelegatingMessageConverter.java index 735d928ac5..35fb9d901c 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/ContentTypeDelegatingMessageConverter.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/ContentTypeDelegatingMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2019 the original author or authors. + * Copyright 2015-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. @@ -33,11 +33,12 @@ * @author Eric Rizzo * @author Gary Russell * @author Artem Bilan + * @author Ngoc Nhan * @since 1.4.2 */ public class ContentTypeDelegatingMessageConverter implements MessageConverter { - private final Map delegates = new HashMap(); + private final Map delegates = new HashMap<>(); private final MessageConverter defaultConverter; @@ -108,9 +109,8 @@ protected MessageConverter getConverterForContentType(String contentType) { if (delegate == null) { throw new MessageConversionException("No delegate converter is specified for content type " + contentType); } - else { - return delegate; - } + + return delegate; } } diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultJackson2JavaTypeMapper.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultJackson2JavaTypeMapper.java index ed3423efa8..380892662b 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultJackson2JavaTypeMapper.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/DefaultJackson2JavaTypeMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-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. @@ -36,6 +36,7 @@ * @author Andreas Asplund * @author Artem Bilan * @author Gary Russell + * @author Ngoc Nhan */ public class DefaultJackson2JavaTypeMapper extends AbstractJavaTypeMapper implements Jackson2JavaTypeMapper { @@ -45,7 +46,7 @@ public class DefaultJackson2JavaTypeMapper extends AbstractJavaTypeMapper implem "java.lang" ); - private final Set trustedPackages = new LinkedHashSet(TRUSTED_PACKAGES); + private final Set trustedPackages = new LinkedHashSet<>(TRUSTED_PACKAGES); private volatile TypePrecedence typePrecedence = TypePrecedence.INFERRED; diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/MarshallingMessageConverter.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/MarshallingMessageConverter.java index 25f77c9cd7..6d94890221 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/MarshallingMessageConverter.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/MarshallingMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-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. @@ -40,6 +40,7 @@ * @author Arjen Poutsma * @author Juergen Hoeller * @author James Carr + * @author Ngoc Nhan * @see org.springframework.amqp.core.AmqpTemplate#convertAndSend(Object) * @see org.springframework.amqp.core.AmqpTemplate#receiveAndConvert() */ @@ -77,10 +78,9 @@ public MarshallingMessageConverter(Marshaller marshaller) { "interface. Please set an Unmarshaller explicitly by using the " + "MarshallingMessageConverter(Marshaller, Unmarshaller) constructor."); } - else { - this.marshaller = marshaller; - this.unmarshaller = (Unmarshaller) marshaller; - } + + this.marshaller = marshaller; + this.unmarshaller = (Unmarshaller) marshaller; } /** diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/MessageConversionException.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/MessageConversionException.java index 376a3c2429..c2e879394d 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/MessageConversionException.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/MessageConversionException.java @@ -23,7 +23,7 @@ * Exception to be thrown by message converters if they encounter a problem with converting a message or object. *

*

- * N.B. this is not an {@link AmqpException} because it is a a client exception, not a protocol or broker + * N.B. this is not an {@link AmqpException} because it is a client exception, not a protocol or broker * problem. *

* diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/MessagingMessageConverter.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/MessagingMessageConverter.java index 0c91aac106..5bc545086b 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/MessagingMessageConverter.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/MessagingMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2020 the original author or authors. + * Copyright 2014-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. @@ -40,6 +40,7 @@ * is considered to be a request). * * @author Stephane Nicoll + * @author Ngoc Nhan * @since 1.4 */ public class MessagingMessageConverter implements MessageConverter, InitializingBean { @@ -104,11 +105,10 @@ public void afterPropertiesSet() { public org.springframework.amqp.core.Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException { - if (!(object instanceof Message)) { + if (!(object instanceof Message input)) { throw new IllegalArgumentException("Could not convert [" + object + "] - only [" + Message.class.getName() + "] is handled by this converter"); } - Message input = (Message) object; this.headerMapper.fromHeaders(input.getHeaders(), messageProperties); org.springframework.amqp.core.Message amqpMessage = this.payloadConverter.toMessage( input.getPayload(), messageProperties); diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/RemoteInvocationResult.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/RemoteInvocationResult.java index 7d08e1d139..6945162895 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/RemoteInvocationResult.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/RemoteInvocationResult.java @@ -26,6 +26,7 @@ * * @author Juergen Hoeller * @author Gary Russell + * @author Ngoc Nhan * @since 3.0 */ public class RemoteInvocationResult implements Serializable { @@ -142,16 +143,13 @@ public boolean hasInvocationTargetException() { @Nullable public Object recreate() throws Throwable { if (this.exception != null) { - Throwable exToThrow = this.exception; - if (this.exception instanceof InvocationTargetException invocationTargetException) { - exToThrow = invocationTargetException.getTargetException(); - } + Throwable exToThrow = this.exception instanceof InvocationTargetException invocationTargetException + ? invocationTargetException.getTargetException() + : this.exception; RemoteInvocationUtils.fillInClientStackTraceIfPossible(exToThrow); throw exToThrow; } - else { - return this.value; - } + return this.value; } } diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/postprocessor/AbstractDecompressingPostProcessor.java b/spring-amqp/src/main/java/org/springframework/amqp/support/postprocessor/AbstractDecompressingPostProcessor.java index 249f0e25e4..3c87829cff 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/postprocessor/AbstractDecompressingPostProcessor.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/postprocessor/AbstractDecompressingPostProcessor.java @@ -38,6 +38,7 @@ * the final content encoding of the decompressed message. * * @author Gary Russell + * @author Ngoc Nhan * @since 1.4.2 */ public abstract class AbstractDecompressingPostProcessor implements MessagePostProcessor, Ordered { @@ -115,9 +116,8 @@ public Message postProcessMessage(Message message) throws AmqpException { throw new AmqpIOException(e); } } - else { - return message; - } + + return message; } /** diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/postprocessor/DelegatingDecompressingPostProcessor.java b/spring-amqp/src/main/java/org/springframework/amqp/support/postprocessor/DelegatingDecompressingPostProcessor.java index e3e247f943..0651734b8c 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/postprocessor/DelegatingDecompressingPostProcessor.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/postprocessor/DelegatingDecompressingPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-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. @@ -30,11 +30,12 @@ * * @author Gary Russell * @author David Diehl + * @author Ngoc Nhan * @since 1.4.2 */ public class DelegatingDecompressingPostProcessor implements MessagePostProcessor, Ordered { - private final Map decompressors = new HashMap(); + private final Map decompressors = new HashMap<>(); private int order; @@ -97,22 +98,20 @@ public Message postProcessMessage(Message message) throws AmqpException { if (encoding == null) { return message; } - else { - int delimAt = encoding.indexOf(':'); - if (delimAt < 0) { - delimAt = encoding.indexOf(','); - } - if (delimAt > 0) { - encoding = encoding.substring(0, delimAt); - } - MessagePostProcessor decompressor = this.decompressors.get(encoding); - if (decompressor != null) { - return decompressor.postProcessMessage(message); - } - else { - return message; - } + + int delimAt = encoding.indexOf(':'); + if (delimAt < 0) { + delimAt = encoding.indexOf(','); + } + if (delimAt > 0) { + encoding = encoding.substring(0, delimAt); } + MessagePostProcessor decompressor = this.decompressors.get(encoding); + if (decompressor != null) { + return decompressor.postProcessMessage(message); + } + + return message; } } diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/postprocessor/MessagePostProcessorUtils.java b/spring-amqp/src/main/java/org/springframework/amqp/support/postprocessor/MessagePostProcessorUtils.java index bf2ed742d8..2c7a8f8b05 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/postprocessor/MessagePostProcessorUtils.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/postprocessor/MessagePostProcessorUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-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. @@ -29,15 +29,16 @@ * Utilities for message post processors. * * @author Gary Russell + * @author Ngoc Nhan * @since 1.4.2 * */ public final class MessagePostProcessorUtils { public static Collection sort(Collection processors) { - List priorityOrdered = new ArrayList(); - List ordered = new ArrayList(); - List unOrdered = new ArrayList(); + List priorityOrdered = new ArrayList<>(); + List ordered = new ArrayList<>(); + List unOrdered = new ArrayList<>(); for (MessagePostProcessor processor : processors) { if (processor instanceof PriorityOrdered) { priorityOrdered.add(processor); @@ -49,7 +50,7 @@ else if (processor instanceof Ordered) { unOrdered.add(processor); } } - List sorted = new ArrayList(); + List sorted = new ArrayList<>(); OrderComparator.sort(priorityOrdered); sorted.addAll(priorityOrdered); OrderComparator.sort(ordered); diff --git a/spring-amqp/src/main/java/org/springframework/amqp/utils/MapBuilder.java b/spring-amqp/src/main/java/org/springframework/amqp/utils/MapBuilder.java index 1fea0e2d65..e3afc18117 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/utils/MapBuilder.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/utils/MapBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 the original author or authors. + * Copyright 2016-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. @@ -26,11 +26,12 @@ * @param the value type. * @author Artem Bilan * @author Gary Russell + * @author Ngoc Nhan * @since 2.0 */ public class MapBuilder, K, V> { - private final Map map = new HashMap(); + private final Map map = new HashMap<>(); public B put(K key, V value) { this.map.put(key, value); diff --git a/spring-amqp/src/main/java/org/springframework/amqp/utils/test/TestUtils.java b/spring-amqp/src/main/java/org/springframework/amqp/utils/test/TestUtils.java index 5934aa6a6a..f8c802e558 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/utils/test/TestUtils.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/utils/test/TestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2019 the original author or authors. + * Copyright 2013-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. @@ -25,6 +25,7 @@ * @author Iwein Fuld * @author Oleg Zhurakousky * @author Gary Russell + * @author Ngoc Nhan * @since 1.2 */ public final class TestUtils { @@ -47,13 +48,14 @@ public static Object getPropertyValue(Object root, String propertyPath) { value = accessor.getPropertyValue(tokens[i]); if (value != null) { accessor = new DirectFieldAccessor(value); + continue; } - else if (i == tokens.length - 1) { + + if (i == tokens.length - 1) { return null; } - else { - throw new IllegalArgumentException("intermediate property '" + tokens[i] + "' is null"); - } + + throw new IllegalArgumentException("intermediate property '" + tokens[i] + "' is null"); } return value; } diff --git a/spring-amqp/src/test/java/org/springframework/amqp/core/AddressTests.java b/spring-amqp/src/test/java/org/springframework/amqp/core/AddressTests.java index feaea573b2..17c24e4130 100644 --- a/spring-amqp/src/test/java/org/springframework/amqp/core/AddressTests.java +++ b/spring-amqp/src/test/java/org/springframework/amqp/core/AddressTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-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. @@ -25,6 +25,7 @@ * @author Mark Fisher * @author Artem Bilan * @author Gary Russell + * @author Ngoc Nhan */ public class AddressTests { @@ -100,6 +101,9 @@ public void testDirectReplyTo() { @Test public void testEquals() { assertThat(new Address("foo/bar")).isEqualTo(new Address("foo/bar")); + assertThat(new Address("foo", null)).isEqualTo(new Address("foo", null)); + assertThat(new Address(null, "bar")).isEqualTo(new Address(null, "bar")); + assertThat(new Address(null, null)).isEqualTo(new Address(null, null)); } } diff --git a/spring-amqp/src/test/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverterTests.java b/spring-amqp/src/test/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverterTests.java index 37e274d314..574fecc06f 100644 --- a/spring-amqp/src/test/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverterTests.java +++ b/spring-amqp/src/test/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -34,6 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; import org.springframework.data.web.JsonPath; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.util.MimeTypeUtils; @@ -54,6 +55,7 @@ * @author Artem Bilan */ @SpringJUnitConfig +@DirtiesContext public class Jackson2JsonMessageConverterTests { public static final String TRUSTED_PACKAGE = Jackson2JsonMessageConverterTests.class.getPackage().getName(); diff --git a/spring-amqp/src/test/java/org/springframework/amqp/support/converter/Jackson2XmlMessageConverterTests.java b/spring-amqp/src/test/java/org/springframework/amqp/support/converter/Jackson2XmlMessageConverterTests.java index d5e3825a0c..836ee45ca0 100644 --- a/spring-amqp/src/test/java/org/springframework/amqp/support/converter/Jackson2XmlMessageConverterTests.java +++ b/spring-amqp/src/test/java/org/springframework/amqp/support/converter/Jackson2XmlMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 the original author or authors. + * Copyright 2018-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. @@ -31,6 +31,7 @@ import org.springframework.amqp.core.MessageProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; @@ -43,6 +44,7 @@ * @since 2.1 */ @SpringJUnitConfig +@DirtiesContext public class Jackson2XmlMessageConverterTests { public static final String TRUSTED_PACKAGE = Jackson2XmlMessageConverterTests.class.getPackage().getName(); diff --git a/spring-rabbit-junit/src/main/java/org/springframework/amqp/rabbit/junit/LongRunning.java b/spring-rabbit-junit/src/main/java/org/springframework/amqp/rabbit/junit/LongRunning.java index f2e3a5d85c..9ea1221489 100644 --- a/spring-rabbit-junit/src/main/java/org/springframework/amqp/rabbit/junit/LongRunning.java +++ b/spring-rabbit-junit/src/main/java/org/springframework/amqp/rabbit/junit/LongRunning.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 the original author or authors. + * Copyright 2017-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. @@ -40,7 +40,7 @@ public @interface LongRunning { /** - * The name of the variable/property used to determine whether long runnning tests + * The name of the variable/property used to determine whether long running tests * should run. * @return the name of the variable/property. */ diff --git a/spring-rabbit-stream/src/main/java/org/springframework/rabbit/stream/listener/StreamListenerContainer.java b/spring-rabbit-stream/src/main/java/org/springframework/rabbit/stream/listener/StreamListenerContainer.java index 925565cb29..842ca8f071 100644 --- a/spring-rabbit-stream/src/main/java/org/springframework/rabbit/stream/listener/StreamListenerContainer.java +++ b/spring-rabbit-stream/src/main/java/org/springframework/rabbit/stream/listener/StreamListenerContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 the original author or authors. + * Copyright 2021-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. @@ -56,6 +56,7 @@ * * @author Gary Russell * @author Christian Tzolov + * @author Ngoc Nhan * @since 2.4 * */ @@ -251,7 +252,7 @@ public void afterPropertiesSet() { public boolean isRunning() { this.lock.lock(); try { - return this.consumers.size() > 0; + return !this.consumers.isEmpty(); } finally { this.lock.unlock(); @@ -262,7 +263,7 @@ public boolean isRunning() { public void start() { this.lock.lock(); try { - if (this.consumers.size() == 0) { + if (this.consumers.isEmpty()) { this.consumerCustomizer.accept(getListenerId(), this.builder); if (this.simpleStream) { this.consumers.add(this.builder.build()); diff --git a/spring-rabbit-stream/src/main/java/org/springframework/rabbit/stream/producer/RabbitStreamTemplate.java b/spring-rabbit-stream/src/main/java/org/springframework/rabbit/stream/producer/RabbitStreamTemplate.java index 0751e7dbd3..a0bde53ad2 100644 --- a/spring-rabbit-stream/src/main/java/org/springframework/rabbit/stream/producer/RabbitStreamTemplate.java +++ b/spring-rabbit-stream/src/main/java/org/springframework/rabbit/stream/producer/RabbitStreamTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 the original author or authors. + * Copyright 2021-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. @@ -55,6 +55,7 @@ * * @author Gary Russell * @author Christian Tzolov + * @author Ngoc Nhan * @since 2.4 * */ @@ -78,8 +79,6 @@ public class RabbitStreamTemplate implements RabbitStreamOperations, Application private boolean streamConverterSet; - private Producer producer; - private String beanName; private ProducerCustomizer producerCustomizer = (name, builder) -> { }; @@ -89,10 +88,12 @@ public class RabbitStreamTemplate implements RabbitStreamOperations, Application @Nullable private RabbitStreamTemplateObservationConvention observationConvention; - private volatile boolean observationRegistryObtained; - private ObservationRegistry observationRegistry; + private volatile Producer producer; + + private volatile boolean observationRegistryObtained; + /** * Construct an instance with the provided {@link Environment}. * @param environment the environment. @@ -107,29 +108,31 @@ public RabbitStreamTemplate(Environment environment, String streamName) { private Producer createOrGetProducer() { - this.lock.lock(); - try { - if (this.producer == null) { - ProducerBuilder builder = this.environment.producerBuilder(); - if (this.superStreamRouting == null) { - builder.stream(this.streamName); - } - else { - builder.superStream(this.streamName) - .routing(this.superStreamRouting); - } - this.producerCustomizer.accept(this.beanName, builder); - this.producer = builder.build(); - if (!this.streamConverterSet) { - ((DefaultStreamMessageConverter) this.streamConverter).setBuilderSupplier( - () -> this.producer.messageBuilder()); + if (this.producer == null) { + this.lock.lock(); + try { + if (this.producer == null) { + ProducerBuilder builder = this.environment.producerBuilder(); + if (this.superStreamRouting == null) { + builder.stream(this.streamName); + } + else { + builder.superStream(this.streamName) + .routing(this.superStreamRouting); + } + this.producerCustomizer.accept(this.beanName, builder); + this.producer = builder.build(); + if (!this.streamConverterSet) { + ((DefaultStreamMessageConverter) this.streamConverter).setBuilderSupplier( + () -> this.producer.messageBuilder()); + } } } - return this.producer; - } - finally { - this.lock.unlock(); + finally { + this.lock.unlock(); + } } + return this.producer; } @Override @@ -305,24 +308,13 @@ private ConfirmationHandler handleConfirm(CompletableFuture future, Obs } else { int code = confStatus.getCode(); - String errorMessage; - switch (code) { - case Constants.CODE_MESSAGE_ENQUEUEING_FAILED: - errorMessage = "Message Enqueueing Failed"; - break; - case Constants.CODE_PRODUCER_CLOSED: - errorMessage = "Producer Closed"; - break; - case Constants.CODE_PRODUCER_NOT_AVAILABLE: - errorMessage = "Producer Not Available"; - break; - case Constants.CODE_PUBLISH_CONFIRM_TIMEOUT: - errorMessage = "Publish Confirm Timeout"; - break; - default: - errorMessage = "Unknown code: " + code; - break; - } + String errorMessage = switch (code) { + case Constants.CODE_MESSAGE_ENQUEUEING_FAILED -> "Message Enqueueing Failed"; + case Constants.CODE_PRODUCER_CLOSED -> "Producer Closed"; + case Constants.CODE_PRODUCER_NOT_AVAILABLE -> "Producer Not Available"; + case Constants.CODE_PUBLISH_CONFIRM_TIMEOUT -> "Publish Confirm Timeout"; + default -> "Unknown code: " + code; + }; StreamSendException ex = new StreamSendException(errorMessage, code); observation.error(ex); observation.stop(); @@ -339,15 +331,17 @@ private ConfirmationHandler handleConfirm(CompletableFuture future, Obs */ @Override public void close() { - this.lock.lock(); - try { - if (this.producer != null) { - this.producer.close(); - this.producer = null; + if (this.producer != null) { + this.lock.lock(); + try { + if (this.producer != null) { + this.producer.close(); + this.producer = null; + } + } + finally { + this.lock.unlock(); } - } - finally { - this.lock.unlock(); } } diff --git a/spring-rabbit-stream/src/main/java/org/springframework/rabbit/stream/support/converter/DefaultStreamMessageConverter.java b/spring-rabbit-stream/src/main/java/org/springframework/rabbit/stream/support/converter/DefaultStreamMessageConverter.java index dd2d765fd7..614f4a6e79 100644 --- a/spring-rabbit-stream/src/main/java/org/springframework/rabbit/stream/support/converter/DefaultStreamMessageConverter.java +++ b/spring-rabbit-stream/src/main/java/org/springframework/rabbit/stream/support/converter/DefaultStreamMessageConverter.java @@ -41,6 +41,7 @@ * Default {@link StreamMessageConverter}. * * @author Gary Russell + * @author Ngoc Nhan * @since 2.4 * */ @@ -105,7 +106,7 @@ public com.rabbitmq.stream.Message fromMessage(Message message) throws MessageCo .acceptIfNotNull(mProps.getGroupSequence(), propsBuilder::groupSequence) .acceptIfNotNull(mProps.getReplyToGroupId(), propsBuilder::replyToGroupId); ApplicationPropertiesBuilder appPropsBuilder = builder.applicationProperties(); - if (mProps.getHeaders().size() > 0) { + if (!mProps.getHeaders().isEmpty()) { mProps.getHeaders().forEach((key, val) -> { mapProp(key, val, appPropsBuilder); }); diff --git a/spring-rabbit-stream/src/test/java/org/springframework/rabbit/stream/config/SuperStreamProvisioningTests.java b/spring-rabbit-stream/src/test/java/org/springframework/rabbit/stream/config/SuperStreamProvisioningTests.java index b87daf8db8..75880c5e2d 100644 --- a/spring-rabbit-stream/src/test/java/org/springframework/rabbit/stream/config/SuperStreamProvisioningTests.java +++ b/spring-rabbit-stream/src/test/java/org/springframework/rabbit/stream/config/SuperStreamProvisioningTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2022-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. @@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** @@ -40,6 +41,7 @@ * */ @SpringJUnitConfig +@DirtiesContext public class SuperStreamProvisioningTests extends AbstractTestContainerTests { @Test diff --git a/spring-rabbit-stream/src/test/java/org/springframework/rabbit/stream/listener/SuperStreamConcurrentSACTests.java b/spring-rabbit-stream/src/test/java/org/springframework/rabbit/stream/listener/SuperStreamConcurrentSACTests.java index c7d34dd83e..50e1ff1058 100644 --- a/spring-rabbit-stream/src/test/java/org/springframework/rabbit/stream/listener/SuperStreamConcurrentSACTests.java +++ b/spring-rabbit-stream/src/test/java/org/springframework/rabbit/stream/listener/SuperStreamConcurrentSACTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-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. @@ -37,6 +37,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.rabbit.stream.config.SuperStream; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import com.rabbitmq.stream.Environment; @@ -49,6 +50,7 @@ * */ @SpringJUnitConfig +@DirtiesContext public class SuperStreamConcurrentSACTests extends AbstractTestContainerTests { @Test diff --git a/spring-rabbit-stream/src/test/java/org/springframework/rabbit/stream/listener/SuperStreamSACTests.java b/spring-rabbit-stream/src/test/java/org/springframework/rabbit/stream/listener/SuperStreamSACTests.java index 1daaca2d33..a474f6926e 100644 --- a/spring-rabbit-stream/src/test/java/org/springframework/rabbit/stream/listener/SuperStreamSACTests.java +++ b/spring-rabbit-stream/src/test/java/org/springframework/rabbit/stream/listener/SuperStreamSACTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-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. @@ -45,6 +45,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.rabbit.stream.config.SuperStream; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import com.rabbitmq.stream.Environment; @@ -57,6 +58,7 @@ * */ @SpringJUnitConfig +@DirtiesContext public class SuperStreamSACTests extends AbstractTestContainerTests { @Test diff --git a/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/context/SpringRabbitTestTests.java b/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/context/SpringRabbitTestTests.java index b4c2fc7e01..697e984a0f 100644 --- a/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/context/SpringRabbitTestTests.java +++ b/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/context/SpringRabbitTestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors. + * Copyright 2020-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. @@ -29,6 +29,7 @@ import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** @@ -39,6 +40,7 @@ @RabbitAvailable @SpringJUnitConfig @SpringRabbitTest +@DirtiesContext public class SpringRabbitTestTests { @Autowired diff --git a/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/examples/TestRabbitTemplateTests.java b/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/examples/TestRabbitTemplateTests.java index 4d8c69586b..6ee389b821 100644 --- a/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/examples/TestRabbitTemplateTests.java +++ b/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/examples/TestRabbitTemplateTests.java @@ -39,6 +39,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import com.rabbitmq.client.AMQP; @@ -53,6 +54,7 @@ * */ @SpringJUnitConfig +@DirtiesContext public class TestRabbitTemplateTests { @Autowired diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/AsyncRabbitTemplate.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/AsyncRabbitTemplate.java index 5d6753f0b9..b8a63057c2 100644 --- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/AsyncRabbitTemplate.java +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/AsyncRabbitTemplate.java @@ -89,6 +89,7 @@ * @author Gary Russell * @author Artem Bilan * @author FengYang Su + * @author Ngoc Nhan * * @since 1.6 */ @@ -483,7 +484,7 @@ public RabbitConverterFuture convertSendAndReceiveAsType(String exchange, private RabbitConverterFuture convertSendAndReceive(String exchange, String routingKey, Object object, MessagePostProcessor messagePostProcessor, ParameterizedTypeReference responseType) { - AsyncCorrelationData correlationData = new AsyncCorrelationData(messagePostProcessor, responseType, + AsyncCorrelationData correlationData = new AsyncCorrelationData<>(messagePostProcessor, responseType, this.enableConfirms); if (this.container != null) { this.template.convertAndSend(exchange, routingKey, object, this.messagePostProcessor, correlationData); @@ -736,7 +737,7 @@ public Message postProcessMessage(Message message, Correlation correlation) thro messageToSend = correlationData.userPostProcessor.postProcessMessage(message); } String correlationId = getOrSetCorrelationIdAndSetReplyTo(messageToSend, correlationData); - correlationData.future = new RabbitConverterFuture(correlationId, message, + correlationData.future = new RabbitConverterFuture<>(correlationId, message, AsyncRabbitTemplate.this::canceler, AsyncRabbitTemplate.this::timeoutTask); if (correlationData.enableConfirms) { correlationData.setId(correlationId); diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/EnableRabbit.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/EnableRabbit.java index b22cc737d9..a83d043419 100644 --- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/EnableRabbit.java +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/EnableRabbit.java @@ -104,7 +104,7 @@ *

Annotated methods can use flexible signature; in particular, it is possible to use * the {@link org.springframework.messaging.Message Message} abstraction and related annotations, * see {@link RabbitListener} Javadoc for more details. For instance, the following would - * inject the content of the message and a a custom "myCounter" AMQP header: + * inject the content of the message and a custom "myCounter" AMQP header: * *

  * @RabbitListener(containerFactory = "myRabbitListenerContainerFactory", queues = "myQueue")
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/MultiRabbitListenerAnnotationBeanPostProcessor.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/MultiRabbitListenerAnnotationBeanPostProcessor.java
index 939bd6b583..0764691e1e 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/MultiRabbitListenerAnnotationBeanPostProcessor.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/MultiRabbitListenerAnnotationBeanPostProcessor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2021 the original author or authors.
+ * Copyright 2020-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.
@@ -24,6 +24,8 @@
 
 import org.springframework.amqp.core.Declarable;
 import org.springframework.amqp.rabbit.config.RabbitListenerConfigUtils;
+import org.springframework.amqp.rabbit.core.RabbitAdmin;
+import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
 import org.springframework.util.StringUtils;
 
 /**
@@ -35,6 +37,7 @@
  * configuration, preventing the server from automatic binding non-related structures.
  *
  * @author Wander Costa
+ * @author Ngoc Nhan
  *
  * @since 2.3
  */
@@ -70,14 +73,32 @@ private RabbitListener proxyIfAdminNotPresent(final RabbitListener rabbitListene
 	 * @return The name of the RabbitAdmin bean.
 	 */
 	protected String resolveMultiRabbitAdminName(RabbitListener rabbitListener) {
-		String admin = super.resolveExpressionAsString(rabbitListener.admin(), "admin");
-		if (!StringUtils.hasText(admin) && StringUtils.hasText(rabbitListener.containerFactory())) {
-			admin = rabbitListener.containerFactory() + RabbitListenerConfigUtils.MULTI_RABBIT_ADMIN_SUFFIX;
+
+		var admin = rabbitListener.admin();
+		if (StringUtils.hasText(admin)) {
+
+			var resolved = super.resolveExpression(admin);
+			if (resolved instanceof RabbitAdmin rabbitAdmin) {
+
+				return rabbitAdmin.getBeanName();
+			}
+
+			return super.resolveExpressionAsString(admin, "admin");
 		}
-		if (!StringUtils.hasText(admin)) {
-			admin = RabbitListenerConfigUtils.RABBIT_ADMIN_BEAN_NAME;
+
+		var containerFactory = rabbitListener.containerFactory();
+		if (StringUtils.hasText(containerFactory)) {
+
+			var resolved = super.resolveExpression(containerFactory);
+			if (resolved instanceof RabbitListenerContainerFactory rlcf) {
+
+				return rlcf.getBeanName() + RabbitListenerConfigUtils.MULTI_RABBIT_ADMIN_SUFFIX;
+			}
+
+			return containerFactory + RabbitListenerConfigUtils.MULTI_RABBIT_ADMIN_SUFFIX;
 		}
-		return admin;
+
+		return RabbitListenerConfigUtils.RABBIT_ADMIN_BEAN_NAME;
 	}
 
 	/**
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/RabbitListenerAnnotationBeanPostProcessor.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/RabbitListenerAnnotationBeanPostProcessor.java
index ffdd652657..e22e1b3860 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/RabbitListenerAnnotationBeanPostProcessor.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/annotation/RabbitListenerAnnotationBeanPostProcessor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2023 the original author or authors.
+ * Copyright 2014-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.
@@ -76,6 +76,7 @@
 import org.springframework.context.expression.StandardBeanExpressionResolver;
 import org.springframework.core.Ordered;
 import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.MergedAnnotation;
 import org.springframework.core.annotation.MergedAnnotations;
 import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
 import org.springframework.core.convert.ConversionService;
@@ -119,6 +120,7 @@
  * @author Gary Russell
  * @author Alex Panchenko
  * @author Artem Bilan
+ * @author Ngoc Nhan
  *
  * @since 1.4
  *
@@ -316,12 +318,12 @@ public Object postProcessAfterInitialization(final Object bean, final String bea
 
 	private TypeMetadata buildMetadata(Class targetClass) {
 		List classLevelListeners = findListenerAnnotations(targetClass);
-		final boolean hasClassLevelListeners = classLevelListeners.size() > 0;
+		final boolean hasClassLevelListeners = !classLevelListeners.isEmpty();
 		final List methods = new ArrayList<>();
 		final List multiMethods = new ArrayList<>();
 		ReflectionUtils.doWithMethods(targetClass, method -> {
 			List listenerAnnotations = findListenerAnnotations(method);
-			if (listenerAnnotations.size() > 0) {
+			if (!listenerAnnotations.isEmpty()) {
 				methods.add(new ListenerMethod(method,
 						listenerAnnotations.toArray(new RabbitListener[listenerAnnotations.size()])));
 			}
@@ -356,14 +358,14 @@ else if (source instanceof Method method) {
 					}
 					return !name.contains("$MockitoMock$");
 				})
-				.map(ann -> ann.synthesize())
+				.map(MergedAnnotation::synthesize)
 				.collect(Collectors.toList());
 	}
 
 	private void processMultiMethodListeners(RabbitListener[] classLevelListeners, Method[] multiMethods,
 			Object bean, String beanName) {
 
-		List checkedMethods = new ArrayList();
+		List checkedMethods = new ArrayList<>();
 		Method defaultMethod = null;
 		for (Method method : multiMethods) {
 			Method checked = checkProxy(method, bean);
@@ -733,7 +735,7 @@ else if (resolvedValueToUse instanceof Iterable) {
 	}
 
 	private String[] registerBeansForDeclaration(RabbitListener rabbitListener, Collection declarables) {
-		List queues = new ArrayList();
+		List queues = new ArrayList<>();
 		if (this.beanFactory instanceof ConfigurableBeanFactory) {
 			for (QueueBinding binding : rabbitListener.bindings()) {
 				String queueName = declareQueue(binding.value(), declarables);
@@ -879,7 +881,7 @@ private Map resolveArguments(Argument[] arguments) {
 				}
 			}
 		}
-		return map.size() < 1 ? null : map;
+		return map.isEmpty() ? null : map;
 	}
 
 	private void addToMap(Map map, String key, Object value, Class typeClass, String typeName) {
@@ -892,7 +894,7 @@ private void addToMap(Map map, String key, Object value, Class messages = new ArrayList();
+	private final List messages = new ArrayList<>();
 
 	private String exchange;
 
@@ -87,7 +88,7 @@ public MessageBatch addToBatch(String exch, String routKey, Message message) {
 		}
 		int bufferUse = Integer.BYTES + message.getBody().length;
 		MessageBatch batch = null;
-		if (this.messages.size() > 0 && this.currentSize + bufferUse > this.bufferLimit) {
+		if (!this.messages.isEmpty() && this.currentSize + bufferUse > this.bufferLimit) {
 			batch = doReleaseBatch();
 			this.exchange = exch;
 			this.routingKey = routKey;
@@ -103,16 +104,15 @@ public MessageBatch addToBatch(String exch, String routKey, Message message) {
 
 	@Override
 	public Date nextRelease() {
-		if (this.messages.size() == 0 || this.timeout <= 0) {
+		if (this.messages.isEmpty() || this.timeout <= 0) {
 			return null;
 		}
-		else if (this.currentSize >= this.bufferLimit) {
+		if (this.currentSize >= this.bufferLimit) {
 			// release immediately, we're already over the limit
 			return new Date();
 		}
-		else {
-			return new Date(System.currentTimeMillis() + this.timeout);
-		}
+
+		return new Date(System.currentTimeMillis() + this.timeout);
 	}
 
 	@Override
@@ -121,13 +121,12 @@ public Collection releaseBatches() {
 		if (batch == null) {
 			return Collections.emptyList();
 		}
-		else {
-			return Collections.singletonList(batch);
-		}
+
+		return Collections.singletonList(batch);
 	}
 
 	private MessageBatch doReleaseBatch() {
-		if (this.messages.size() < 1) {
+		if (this.messages.isEmpty()) {
 			return null;
 		}
 		Message message = assembleMessage();
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AbstractExchangeParser.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AbstractExchangeParser.java
index 705fd84056..434e57d676 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AbstractExchangeParser.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AbstractExchangeParser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-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.
@@ -34,6 +34,7 @@
  * @author Gary Russell
  * @author Felipe Gutierrez
  * @author Artem Bilan
+ * @author Ngoc Nhan
  *
  */
 public abstract class AbstractExchangeParser extends AbstractSingleBeanDefinitionParser {
@@ -144,7 +145,7 @@ private void parseArguments(Element element, String argumentsElementName, Parser
 			Map map = parserContext.getDelegate().parseMapElement(argumentsElement,
 					builder.getRawBeanDefinition());
 			if (StringUtils.hasText(ref)) {
-				if (map != null && map.size() > 0) {
+				if (map != null && !map.isEmpty()) {
 					parserContext.getReaderContext().error("You cannot have both a 'ref' and a nested map", element);
 				}
 				if (propertyName == null) {
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AbstractRabbitListenerContainerFactory.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AbstractRabbitListenerContainerFactory.java
index f1ec63668a..bdbce98737 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AbstractRabbitListenerContainerFactory.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AbstractRabbitListenerContainerFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2023 the original author or authors.
+ * Copyright 2014-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.
@@ -344,7 +344,7 @@ public void setObservationConvention(RabbitListenerObservationConvention observa
 	/**
 	 * Set to true to stop the container after the current message(s) are processed and
 	 * requeue any prefetched. Useful when using exclusive or single-active consumers.
-	 * @param forceStop true to stop when current messsage(s) are processed.
+	 * @param forceStop true to stop when current message(s) are processed.
 	 * @since 2.4.15
 	 */
 	public void setForceStop(boolean forceStop) {
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/BaseRabbitListenerContainerFactory.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/BaseRabbitListenerContainerFactory.java
index 5a1fab024a..6363bbe18c 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/BaseRabbitListenerContainerFactory.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/BaseRabbitListenerContainerFactory.java
@@ -43,6 +43,7 @@
  * @param  the container type that the factory creates.
  *
  * @author Gary Russell
+ * @author Ngoc Nhan
  * @since 2.4
  *
  */
@@ -67,6 +68,8 @@ public abstract class BaseRabbitListenerContainerFactory map = new ManagedMap();
+			ManagedMap map = new ManagedMap<>();
 			map.put(new TypedStringValue(key), new TypedStringValue(value));
 			builder.addPropertyValue("arguments", map);
 		}
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/ListenerContainerFactoryBean.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/ListenerContainerFactoryBean.java
index 0a070e1eca..b2bc26fb8a 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/ListenerContainerFactoryBean.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/ListenerContainerFactoryBean.java
@@ -56,6 +56,7 @@
  * @author Artem Bilan
  * @author Johno Crawford
  * @author Jeonggi Kim
+ * @author Ngoc Nhan
  *
  * @since 2.0
  *
@@ -542,7 +543,7 @@ protected AbstractMessageListenerContainer createInstance() { // NOSONAR complex
 					.acceptIfNotNull(this.exclusiveConsumerExceptionLogger,
 							container::setExclusiveConsumerExceptionLogger)
 					.acceptIfNotNull(this.micrometerEnabled, container::setMicrometerEnabled)
-					.acceptIfCondition(this.micrometerTags.size() > 0, this.micrometerTags,
+					.acceptIfCondition(!this.micrometerTags.isEmpty(), this.micrometerTags,
 							container::setMicrometerTags);
 			if (this.smlcCustomizer != null && this.type.equals(Type.simple)) {
 				this.smlcCustomizer.configure((SimpleMessageListenerContainer) container);
@@ -574,14 +575,13 @@ private AbstractMessageListenerContainer createContainer() {
 					.acceptIfNotNull(this.retryDeclarationInterval, container::setRetryDeclarationInterval);
 			return container;
 		}
-		else {
-			DirectMessageListenerContainer container = new DirectMessageListenerContainer(this.connectionFactory);
-			JavaUtils.INSTANCE
-					.acceptIfNotNull(this.consumersPerQueue, container::setConsumersPerQueue)
-					.acceptIfNotNull(this.taskScheduler, container::setTaskScheduler)
-					.acceptIfNotNull(this.monitorInterval, container::setMonitorInterval);
-			return container;
-		}
+
+		DirectMessageListenerContainer container = new DirectMessageListenerContainer(this.connectionFactory);
+		JavaUtils.INSTANCE
+				.acceptIfNotNull(this.consumersPerQueue, container::setConsumersPerQueue)
+				.acceptIfNotNull(this.taskScheduler, container::setTaskScheduler)
+				.acceptIfNotNull(this.monitorInterval, container::setMonitorInterval);
+		return container;
 	}
 
 	@Override
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/ListenerContainerParser.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/ListenerContainerParser.java
index 0fb64b643c..71acf60981 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/ListenerContainerParser.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/ListenerContainerParser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-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.
@@ -42,6 +42,7 @@
 /**
  * @author Mark Fisher
  * @author Gary Russell
+ * @author Ngoc Nhan
  * @since 1.0
  */
 class ListenerContainerParser implements BeanDefinitionParser {
@@ -100,8 +101,8 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
 		}
 
 		List childElements = DomUtils.getChildElementsByTagName(element, LISTENER_ELEMENT);
-		for (int i = 0; i < childElements.size(); i++) {
-			parseListener(childElements.get(i), element, parserContext, containerList);
+		for (Element childElement : childElements) {
+			parseListener(childElement, element, parserContext, containerList);
 		}
 
 		parserContext.popAndRegisterContainingComponent();
@@ -188,22 +189,22 @@ private void parseListener(Element listenerEle, Element containerEle, ParserCont
 			}
 			else {
 				String[] names = StringUtils.commaDelimitedListToStringArray(queues);
-				List values = new ManagedList();
-				for (int i = 0; i < names.length; i++) {
-					values.add(new RuntimeBeanReference(names[i].trim()));
+				List values = new ManagedList<>();
+				for (String name : names) {
+					values.add(new RuntimeBeanReference(name.trim()));
 				}
 				containerDef.getPropertyValues().add("queues", values);
 			}
 		}
 
-		ManagedMap args = new ManagedMap();
+		ManagedMap args = new ManagedMap<>();
 
 		String priority = listenerEle.getAttribute("priority");
 		if (StringUtils.hasText(priority)) {
 			args.put("x-priority", new TypedStringValue(priority, Integer.class));
 		}
 
-		if (args.size() > 0) {
+		if (!args.isEmpty()) {
 			containerDef.getPropertyValues().add("consumerArguments", args);
 		}
 
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/NamespaceUtils.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/NamespaceUtils.java
index afdd5ada8a..5b99acef0f 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/NamespaceUtils.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/NamespaceUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-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.
@@ -37,6 +37,7 @@
  * @author Mark Pollack
  * @author Dave Syer
  * @author Gary Russell
+ * @author Ngoc Nhan
  *
  */
 public abstract class NamespaceUtils {
@@ -125,7 +126,7 @@ public static boolean addConstructorArgValueIfAttributeDefined(BeanDefinitionBui
 	 * @param builder the bean definition builder to be configured
 	 * @param element the XML element where the attribute should be defined
 	 * @param attributeName the name of the attribute whose value will be used as a constructor argument
-	 * @param defaultValue the default value to use if the attirbute is not set
+	 * @param defaultValue the default value to use if the attribute is not set
 	 */
 	public static void addConstructorArgBooleanValueIfAttributeDefined(BeanDefinitionBuilder builder, Element element,
 			String attributeName, boolean defaultValue) {
@@ -249,7 +250,7 @@ public static void parseDeclarationControls(Element element, BeanDefinitionBuild
 		String admins = element.getAttribute("declared-by");
 		if (StringUtils.hasText(admins)) {
 			String[] adminBeanNames = admins.split(",");
-			ManagedList adminBeanRefs = new ManagedList();
+			ManagedList adminBeanRefs = new ManagedList<>();
 			for (String adminBeanName : adminBeanNames) {
 				adminBeanRefs.add(new RuntimeBeanReference(adminBeanName.trim()));
 			}
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/QueueParser.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/QueueParser.java
index 4bce257e1a..38314dad06 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/QueueParser.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/QueueParser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-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.
@@ -33,6 +33,7 @@
  * @author Gary Russell
  * @author Felipe Gutierrez
  * @author Artem Bilan
+ * @author Ngoc Nhan
  *
  */
 public class QueueParser extends AbstractSingleBeanDefinitionParser {
@@ -134,7 +135,7 @@ private void parseArguments(Element element, ParserContext parserContext, BeanDe
 			Map map = parserContext.getDelegate().parseMapElement(argumentsElement,
 					builder.getRawBeanDefinition());
 			if (StringUtils.hasText(ref)) {
-				if (map != null && map.size() > 0) {
+				if (map != null && !map.isEmpty()) {
 					parserContext.getReaderContext()
 							.error("You cannot have both a 'ref' and a nested map", element);
 				}
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/RabbitNamespaceUtils.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/RabbitNamespaceUtils.java
index 69ba403406..c219c7bf7c 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/RabbitNamespaceUtils.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/RabbitNamespaceUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-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.
@@ -358,28 +358,22 @@ public static BeanDefinition parseContainer(Element containerEle, ParserContext
 	}
 
 	private static AcknowledgeMode parseAcknowledgeMode(Element ele, ParserContext parserContext) {
-		AcknowledgeMode acknowledgeMode = null;
 		String acknowledge = ele.getAttribute(ACKNOWLEDGE_ATTRIBUTE);
 		if (StringUtils.hasText(acknowledge)) {
-			if (ACKNOWLEDGE_AUTO.equals(acknowledge)) {
-				acknowledgeMode = AcknowledgeMode.AUTO;
-			}
-			else if (ACKNOWLEDGE_MANUAL.equals(acknowledge)) {
-				acknowledgeMode = AcknowledgeMode.MANUAL;
-			}
-			else if (ACKNOWLEDGE_NONE.equals(acknowledge)) {
-				acknowledgeMode = AcknowledgeMode.NONE;
-			}
-			else {
-				parserContext.getReaderContext().error(
+			return switch (acknowledge) {
+				case ACKNOWLEDGE_AUTO -> AcknowledgeMode.AUTO;
+				case ACKNOWLEDGE_MANUAL -> AcknowledgeMode.MANUAL;
+				case ACKNOWLEDGE_NONE -> AcknowledgeMode.NONE;
+				default -> {
+					parserContext.getReaderContext().error(
 						"Invalid listener container 'acknowledge' setting [" + acknowledge
-								+ "]: only \"auto\", \"manual\", and \"none\" supported.", ele);
-			}
-			return acknowledgeMode;
-		}
-		else {
-			return null;
+							+ "]: only \"auto\", \"manual\", and \"none\" supported.", ele);
+					yield null;
+				}
+			};
 		}
+
+		return null;
 	}
 
 }
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/StatefulRetryOperationsInterceptorFactoryBean.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/StatefulRetryOperationsInterceptorFactoryBean.java
index 3aa40f94ef..4e79caaa98 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/StatefulRetryOperationsInterceptorFactoryBean.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/StatefulRetryOperationsInterceptorFactoryBean.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-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.
@@ -27,12 +27,14 @@
 import org.springframework.amqp.rabbit.retry.MessageKeyGenerator;
 import org.springframework.amqp.rabbit.retry.MessageRecoverer;
 import org.springframework.amqp.rabbit.retry.NewMessageIdentifier;
+import org.springframework.lang.Nullable;
 import org.springframework.retry.RetryOperations;
 import org.springframework.retry.interceptor.MethodArgumentsKeyGenerator;
 import org.springframework.retry.interceptor.MethodInvocationRecoverer;
 import org.springframework.retry.interceptor.NewMethodArgumentsIdentifier;
 import org.springframework.retry.interceptor.StatefulRetryOperationsInterceptor;
 import org.springframework.retry.support.RetryTemplate;
+import org.springframework.util.Assert;
 
 /**
  * Convenient factory bean for creating a stateful retry interceptor for use in a message listener container, giving you
@@ -47,6 +49,7 @@
  *
  * @author Dave Syer
  * @author Gary Russell
+ * @author Ngoc Nhan
  *
  * @see RetryOperations#execute(org.springframework.retry.RetryCallback, org.springframework.retry.RecoveryCallback,
  * org.springframework.retry.RetryState)
@@ -60,8 +63,8 @@ public class StatefulRetryOperationsInterceptorFactoryBean extends AbstractRetry
 
 	private NewMessageIdentifier newMessageIdentifier;
 
-	public void setMessageKeyGenerator(MessageKeyGenerator messageKeyGeneretor) {
-		this.messageKeyGenerator = messageKeyGeneretor;
+	public void setMessageKeyGenerator(MessageKeyGenerator messageKeyGenerator) {
+		this.messageKeyGenerator = messageKeyGenerator;
 	}
 
 	public void setNewMessageIdentifier(NewMessageIdentifier newMessageIdentifier) {
@@ -90,9 +93,8 @@ private NewMethodArgumentsIdentifier createNewItemIdentifier() {
 			if (StatefulRetryOperationsInterceptorFactoryBean.this.newMessageIdentifier == null) {
 				return !message.getMessageProperties().isRedelivered();
 			}
-			else {
-				return StatefulRetryOperationsInterceptorFactoryBean.this.newMessageIdentifier.isNew(message);
-			}
+
+			return StatefulRetryOperationsInterceptorFactoryBean.this.newMessageIdentifier.isNew(message);
 		};
 	}
 
@@ -120,6 +122,7 @@ else if (arg instanceof List && messageRecoverer instanceof MessageBatchRecovere
 	private MethodArgumentsKeyGenerator createKeyGenerator() {
 		return args -> {
 			Message message = argToMessage(args);
+			Assert.notNull(message, "The 'args' must not convert to null");
 			if (StatefulRetryOperationsInterceptorFactoryBean.this.messageKeyGenerator == null) {
 				String messageId = message.getMessageProperties().getMessageId();
 				if (messageId == null && message.getMessageProperties().isRedelivered()) {
@@ -127,23 +130,20 @@ private MethodArgumentsKeyGenerator createKeyGenerator() {
 				}
 				return messageId;
 			}
-			else {
-				return StatefulRetryOperationsInterceptorFactoryBean.this.messageKeyGenerator.getKey(message);
-			}
+			return StatefulRetryOperationsInterceptorFactoryBean.this.messageKeyGenerator.getKey(message);
 		};
 	}
 
-	@SuppressWarnings("unchecked")
+	@Nullable
 	private Message argToMessage(Object[] args) {
 		Object arg = args[1];
-		Message message = null;
 		if (arg instanceof Message msg) {
-			message = msg;
+			return msg;
 		}
-		else if (arg instanceof List) {
-			message = ((List) arg).get(0);
+		if (arg instanceof List list) {
+			return (Message) list.get(0);
 		}
-		return message;
+		return null;
 	}
 
 	@Override
@@ -151,9 +151,4 @@ public Class getObjectType() {
 		return StatefulRetryOperationsInterceptor.class;
 	}
 
-	@Override
-	public boolean isSingleton() {
-		return true;
-	}
-
 }
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/TemplateParser.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/TemplateParser.java
index 5259fa0e25..c9c5c3f908 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/TemplateParser.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/TemplateParser.java
@@ -34,6 +34,7 @@
  * @author Dave Syer
  * @author Gary Russell
  * @author Artem Bilan
+ * @author Ngoc Nhan
  */
 class TemplateParser extends AbstractSingleBeanDefinitionParser {
 
@@ -160,7 +161,7 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
 		BeanDefinition replyContainer = null;
 		Element childElement = null;
 		List childElements = DomUtils.getChildElementsByTagName(element, LISTENER_ELEMENT);
-		if (childElements.size() > 0) {
+		if (!childElements.isEmpty()) {
 			childElement = childElements.get(0);
 		}
 		if (childElement != null) {
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/AbstractConnectionFactory.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/AbstractConnectionFactory.java
index 33cb11dccf..b7ecb1a80d 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/AbstractConnectionFactory.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/AbstractConnectionFactory.java
@@ -166,6 +166,7 @@ public void handleRecovery(Recoverable recoverable) {
 
 	@Nullable
 	private BackOff connectionCreatingBackOff;
+
 	/**
 	 * Create a new AbstractConnectionFactory for the given target ConnectionFactory, with no publisher connection
 	 * factory.
@@ -343,7 +344,16 @@ public int getPort() {
 
 	/**
 	 * Set addresses for clustering. This property overrides the host+port properties if not empty.
-	 * @param addresses list of addresses with form "host[:port],..."
+	 * @param addresses list of addresses in form {@code host[:port]}.
+	 * @since 3.2.1
+	 */
+	public void setAddresses(List addresses) {
+		Assert.notEmpty(addresses, "Addresses must not be empty");
+		setAddresses(String.join(",", addresses));
+	}
+	/**
+	 * Set addresses for clustering. This property overrides the host+port properties if not empty.
+	 * @param addresses list of addresses with form {@code host1[:port1],host2[:port2],...}.
 	 */
 	public void setAddresses(String addresses) {
 		this.lock.lock();
@@ -478,8 +488,9 @@ protected ExecutorService getExecutorService() {
 	}
 
 	/**
-	 * How long to wait (milliseconds) for a response to a connection close operation from the broker; default 30000 (30
-	 * seconds).
+	 * How long to wait (milliseconds) for a response to a connection close operation from the broker;
+	 * default 30000 (30 seconds).
+	 * Also used for {@link com.rabbitmq.client.Channel#waitForConfirms()}.
 	 * @param closeTimeout the closeTimeout to set.
 	 */
 	public void setCloseTimeout(int closeTimeout) {
@@ -580,8 +591,8 @@ public ConnectionFactory getPublisherConnectionFactory() {
 	protected final Connection createBareConnection() {
 		try {
 			String connectionName = this.connectionNameStrategy.obtainNewConnectionName(this);
-
 			com.rabbitmq.client.Connection rabbitConnection = connect(connectionName);
+			rabbitConnection.addShutdownListener(this);
 			Connection connection = new SimpleConnection(rabbitConnection, this.closeTimeout,
 					this.connectionCreatingBackOff == null ? null : this.connectionCreatingBackOff.start());
 			if (rabbitConnection instanceof AutorecoveringConnection auto) {
@@ -732,16 +743,8 @@ public String toString() {
 		}
 	}
 
-	private static final class ConnectionBlockedListener implements BlockedListener {
-
-		private final Connection connection;
-
-		private final ApplicationEventPublisher applicationEventPublisher;
-
-		ConnectionBlockedListener(Connection connection, ApplicationEventPublisher applicationEventPublisher) {
-			this.connection = connection;
-			this.applicationEventPublisher = applicationEventPublisher;
-		}
+	private record ConnectionBlockedListener(Connection connection, ApplicationEventPublisher applicationEventPublisher)
+			implements BlockedListener {
 
 		@Override
 		public void handleBlocked(String reason) {
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/AbstractRoutingConnectionFactory.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/AbstractRoutingConnectionFactory.java
index fdf057a34b..be70f77cc5 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/AbstractRoutingConnectionFactory.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/AbstractRoutingConnectionFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-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.
@@ -36,15 +36,16 @@
  * @author Josh Chappelle
  * @author Gary Russell
  * @author Leonardo Ferreira
+ * @author Ngoc Nhan
  * @since 1.3
  */
 public abstract class AbstractRoutingConnectionFactory implements ConnectionFactory, RoutingConnectionFactory,
 		InitializingBean, DisposableBean {
 
 	private final Map targetConnectionFactories =
-			new ConcurrentHashMap();
+			new ConcurrentHashMap<>();
 
-	private final List connectionListeners = new ArrayList();
+	private final List connectionListeners = new ArrayList<>();
 
 	private ConnectionFactory defaultTargetConnectionFactory;
 
@@ -68,7 +69,7 @@ public void setTargetConnectionFactories(Map targetCo
 		Assert.noNullElements(targetConnectionFactories.values().toArray(),
 				"'targetConnectionFactories' cannot have null values.");
 		this.targetConnectionFactories.putAll(targetConnectionFactories);
-		targetConnectionFactories.values().stream().forEach(cf -> checkConfirmsAndReturns(cf));
+		targetConnectionFactories.values().forEach(this::checkConfirmsAndReturns);
 	}
 
 	/**
@@ -292,7 +293,7 @@ public void destroy() {
 
 	@Override
 	public void resetConnection() {
-		this.targetConnectionFactories.values().forEach(factory -> factory.resetConnection());
+		this.targetConnectionFactories.values().forEach(ConnectionFactory::resetConnection);
 		this.defaultTargetConnectionFactory.resetConnection();
 	}
 
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactory.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactory.java
index f14aa989b9..302b18a5cd 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactory.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactory.java
@@ -861,8 +861,6 @@ private void refreshProxyConnection(ChannelCachingConnectionProxy connection) {
 	 */
 	@Override
 	public final void destroy() {
-		super.destroy();
-		resetConnection();
 		if (getContextStopped()) {
 			this.stopped = true;
 			this.connectionLock.lock();
@@ -890,6 +888,8 @@ public final void destroy() {
 				this.connectionLock.unlock();
 			}
 		}
+		super.destroy();
+		resetConnection();
 	}
 
 	/**
@@ -1087,8 +1087,6 @@ public String toString() {
 
 	private final class CachedChannelInvocationHandler implements InvocationHandler {
 
-		private static final int ASYNC_CLOSE_TIMEOUT = 5_000;
-
 		private final ChannelCachingConnectionProxy theConnection;
 
 		private final Deque channelList;
@@ -1302,7 +1300,7 @@ private void returnToCache(ChannelProxy proxy) {
 					getChannelsExecutor()
 							.execute(() -> {
 								try {
-									publisherCallbackChannel.waitForConfirms(ASYNC_CLOSE_TIMEOUT);
+									publisherCallbackChannel.waitForConfirms(getCloseTimeout());
 								}
 								catch (InterruptedException ex) {
 									Thread.currentThread().interrupt();
@@ -1426,10 +1424,10 @@ private void asyncClose() {
 				executorService.execute(() -> {
 					try {
 						if (ConfirmType.CORRELATED.equals(CachingConnectionFactory.this.confirmType)) {
-							channel.waitForConfirmsOrDie(ASYNC_CLOSE_TIMEOUT);
+							channel.waitForConfirmsOrDie(getCloseTimeout());
 						}
 						else {
-							Thread.sleep(ASYNC_CLOSE_TIMEOUT);
+							Thread.sleep(5_000); // NOSONAR - some time to give the channel a chance to ack
 						}
 					}
 					catch (@SuppressWarnings(UNUSED) InterruptedException e1) {
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CompositeChannelListener.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CompositeChannelListener.java
index 81c7dd532f..66399f31e4 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CompositeChannelListener.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CompositeChannelListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-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.
@@ -25,11 +25,12 @@
 /**
  * @author Dave Syer
  * @author Gary Russell
+ * @author Ngoc Nhan
  *
  */
 public class CompositeChannelListener implements ChannelListener {
 
-	private List delegates = new ArrayList();
+	private List delegates = new ArrayList<>();
 
 	public void onCreate(Channel channel, boolean transactional) {
 		for (ChannelListener delegate : this.delegates) {
@@ -45,7 +46,7 @@ public void onShutDown(ShutdownSignalException signal) {
 	}
 
 	public void setDelegates(List delegates) {
-		this.delegates = new ArrayList(delegates);
+		this.delegates = new ArrayList<>(delegates);
 	}
 
 	public void addDelegate(ChannelListener delegate) {
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CompositeConnectionListener.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CompositeConnectionListener.java
index ce1b01a7b2..e8d9746446 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CompositeConnectionListener.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CompositeConnectionListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-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.
@@ -23,15 +23,16 @@
 import com.rabbitmq.client.ShutdownSignalException;
 
 /**
- * A composite listener that invokes its delegages in turn.
+ * A composite listener that invokes its delegates in turn.
  *
  * @author Dave Syer
  * @author Gary Russell
+ * @author Ngoc Nhan
  *
  */
 public class CompositeConnectionListener implements ConnectionListener {
 
-	private List delegates = new CopyOnWriteArrayList();
+	private List delegates = new CopyOnWriteArrayList<>();
 
 	@Override
 	public void onCreate(Connection connection) {
@@ -54,7 +55,7 @@ public void onFailed(Exception exception) {
 	}
 
 	public void setDelegates(List delegates) {
-		this.delegates = new ArrayList(delegates);
+		this.delegates = new ArrayList<>(delegates);
 	}
 
 	public void addDelegate(ConnectionListener delegate) {
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ConsumerChannelRegistry.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ConsumerChannelRegistry.java
index 0aac6947b3..e72c338b04 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ConsumerChannelRegistry.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ConsumerChannelRegistry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-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.
@@ -31,6 +31,7 @@
  * tangle with RabbitResourceHolder.
  *
  * @author Gary Russell
+ * @author Ngoc Nhan
  * @since 1.2
  *
  */
@@ -39,7 +40,7 @@ public final class ConsumerChannelRegistry {
 	private static final Log logger = LogFactory.getLog(ConsumerChannelRegistry.class); // NOSONAR - lower case
 
 	private static final ThreadLocal consumerChannel // NOSONAR - lower case
-		= new ThreadLocal();
+		= new ThreadLocal<>();
 
 	private ConsumerChannelRegistry() {
 	}
@@ -83,11 +84,9 @@ public static void unRegisterConsumerChannel() {
 	@Nullable
 	public static Channel getConsumerChannel() {
 		ChannelHolder channelHolder = consumerChannel.get();
-		Channel channel = null;
-		if (channelHolder != null) {
-			channel = channelHolder.getChannel();
-		}
-		return channel;
+		return channelHolder != null
+				? channelHolder.getChannel()
+				: null;
 	}
 
 	/**
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/LocalizedQueueConnectionFactory.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/LocalizedQueueConnectionFactory.java
index e84ed71190..c27986322b 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/LocalizedQueueConnectionFactory.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/LocalizedQueueConnectionFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015-2023 the original author or authors.
+ * Copyright 2015-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.
@@ -52,6 +52,7 @@
  *
  * @author Gary Russell
  * @author Christian Tzolov
+ * @author Ngoc Nhan
  * @since 1.2
  */
 public class LocalizedQueueConnectionFactory implements ConnectionFactory, RoutingConnectionFactory, DisposableBean,
@@ -61,13 +62,13 @@ public class LocalizedQueueConnectionFactory implements ConnectionFactory, Routi
 
 	private final Lock lock = new ReentrantLock();
 
-	private final Map nodeFactories = new HashMap();
+	private final Map nodeFactories = new HashMap<>();
 
 	private final ConnectionFactory defaultConnectionFactory;
 
 	private final String[] adminUris;
 
-	private final Map nodeToAddress = new HashMap();
+	private final Map nodeToAddress = new HashMap<>();
 
 	private final String vhost;
 
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/PendingConfirm.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/PendingConfirm.java
index 95c71fe744..5aa67760c9 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/PendingConfirm.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/PendingConfirm.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-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.
@@ -27,6 +27,7 @@
  * expired. It also holds {@link CorrelationData} for
  * the client to correlate a confirm with a sent message.
  * @author Gary Russell
+ * @author Ngoc Nhan
  * @since 1.0.1
  *
  */
@@ -115,7 +116,7 @@ public void setReturned(boolean isReturned) {
 	 * @since 2.2.10
 	 */
 	public boolean waitForReturnIfNeeded() throws InterruptedException {
-		return this.returned ? this.latch.await(RETURN_CALLBACK_TIMEOUT, TimeUnit.SECONDS) : true;
+		return !this.returned || this.latch.await(RETURN_CALLBACK_TIMEOUT, TimeUnit.SECONDS);
 	}
 
 	/**
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/PooledChannelConnectionFactory.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/PooledChannelConnectionFactory.java
index a458be72ab..321c8925ab 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/PooledChannelConnectionFactory.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/PooledChannelConnectionFactory.java
@@ -55,6 +55,7 @@
  * @author Gary Russell
  * @author Leonardo Ferreira
  * @author Christian Tzolov
+ * @author Ngoc Nhan
  * @since 2.3
  *
  */
@@ -255,23 +256,21 @@ private Channel createProxy(Channel channel, boolean transacted) {
 			Advice advice =
 					(MethodInterceptor) invocation -> {
 						String method = invocation.getMethod().getName();
-						switch (method) {
-						case "close":
-							handleClose(channel, transacted, proxy);
-							return null;
-						case "getTargetChannel":
-							return channel;
-						case "isTransactional":
-							return transacted;
-						case "confirmSelect":
-							confirmSelected.set(true);
-							return channel.confirmSelect();
-						case "isConfirmSelected":
-							return confirmSelected.get();
-						case "isPublisherConfirms":
-							return false;
-						}
-						return null;
+						return switch (method) {
+							case "close" -> {
+								handleClose(channel, transacted, proxy);
+								yield null;
+							}
+							case "getTargetChannel" -> channel;
+							case "isTransactional" -> transacted;
+							case "confirmSelect" -> {
+								confirmSelected.set(true);
+								yield channel.confirmSelect();
+							}
+							case "isConfirmSelected" -> confirmSelected.get();
+							case "isPublisherConfirms" -> false;
+							default -> null;
+						};
 					};
 			NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(advice);
 			advisor.addMethodName("close");
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/PublisherCallbackChannelImpl.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/PublisherCallbackChannelImpl.java
index c298c118a9..74fb85bc95 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/PublisherCallbackChannelImpl.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/PublisherCallbackChannelImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-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.
@@ -91,6 +91,7 @@
  * @author Arnaud Cogoluègnes
  * @author Artem Bilan
  * @author Christian Tzolov
+ * @author Ngoc Nhan
  *
  * @since 1.0.1
  *
@@ -903,12 +904,12 @@ public int getPendingConfirmsCount() {
 	@Override
 	public void addListener(Listener listener) {
 		Assert.notNull(listener, "Listener cannot be null");
-		if (this.listeners.size() == 0) {
+		if (this.listeners.isEmpty()) {
 			this.delegate.addConfirmListener(this);
 			this.delegate.addReturnListener(this);
 		}
 		if (this.listeners.putIfAbsent(listener.getUUID(), listener) == null) {
-			this.pendingConfirms.put(listener, new ConcurrentSkipListMap());
+			this.pendingConfirms.put(listener, new ConcurrentSkipListMap<>());
 			if (this.logger.isDebugEnabled()) {
 				this.logger.debug("Added listener " + listener);
 			}
@@ -921,27 +922,26 @@ public Collection expire(Listener listener, long cutoffTime) {
 		try {
 			SortedMap pendingConfirmsForListener = this.pendingConfirms.get(listener);
 			if (pendingConfirmsForListener == null) {
-				return Collections.emptyList();
+				return Collections.emptyList();
 			}
-			else {
-				List expired = new ArrayList();
-				Iterator> iterator = pendingConfirmsForListener.entrySet().iterator();
-				while (iterator.hasNext()) {
-					PendingConfirm pendingConfirm = iterator.next().getValue();
-					if (pendingConfirm.getTimestamp() < cutoffTime) {
-						expired.add(pendingConfirm);
-						iterator.remove();
-						CorrelationData correlationData = pendingConfirm.getCorrelationData();
-						if (correlationData != null && StringUtils.hasText(correlationData.getId())) {
-							this.pendingReturns.remove(correlationData.getId()); // NOSONAR never null
-						}
-					}
-					else {
-						break;
+
+			List expired = new ArrayList<>();
+			Iterator> iterator = pendingConfirmsForListener.entrySet().iterator();
+			while (iterator.hasNext()) {
+				PendingConfirm pendingConfirm = iterator.next().getValue();
+				if (pendingConfirm.getTimestamp() < cutoffTime) {
+					expired.add(pendingConfirm);
+					iterator.remove();
+					CorrelationData correlationData = pendingConfirm.getCorrelationData();
+					if (correlationData != null && StringUtils.hasText(correlationData.getId())) {
+						this.pendingReturns.remove(correlationData.getId()); // NOSONAR never null
 					}
 				}
-				return expired;
+				else {
+					break;
+				}
 			}
+			return expired;
 		}
 		finally {
 			this.lock.unlock();
@@ -1025,7 +1025,7 @@ private void processMultipleAck(long seq, boolean ack) {
 		 */
 		Map involvedListeners = this.listenerForSeq.headMap(seq + 1);
 		// eliminate duplicates
-		Set listenersForAcks = new HashSet(involvedListeners.values());
+		Set listenersForAcks = new HashSet<>(involvedListeners.values());
 		for (Listener involvedListener : listenersForAcks) {
 			// find all unack'd confirms for this listener and handle them
 			SortedMap confirmsMap = this.pendingConfirms.get(involvedListener);
@@ -1047,7 +1047,7 @@ private void processMultipleAck(long seq, boolean ack) {
 				}
 			}
 		}
-		List seqs = new ArrayList(involvedListeners.keySet());
+		List seqs = new ArrayList<>(involvedListeners.keySet());
 		for (Long key : seqs) {
 			this.listenerForSeq.remove(key);
 		}
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitConnectionFactoryBean.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitConnectionFactoryBean.java
index d12361ee1d..944402231e 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitConnectionFactoryBean.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitConnectionFactoryBean.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-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.
@@ -63,7 +63,7 @@
  * optionally enabling SSL, with or without certificate validation. When
  * {@link #setSslPropertiesLocation(Resource) sslPropertiesLocation} is not null, the
  * default implementation loads a {@code PKCS12} keystore and a {@code JKS} truststore
- * using the supplied properties and intializes key and trust manager factories, using
+ * using the supplied properties and initializes key and trust manager factories, using
  * algorithm {@code SunX509} by default. These are then used to initialize an
  * {@link SSLContext} using the {@link #setSslAlgorithm(String) sslAlgorithm} (default
  * TLSv1.2, falling back to TLSv1.1, if 1.2 is not available).
@@ -79,6 +79,7 @@
  * @author Hareendran
  * @author Dominique Villard
  * @author Zachary DeLuca
+ * @author Ngoc Nhan
  *
  * @since 1.4
  */
@@ -360,12 +361,11 @@ protected String getKeyStoreType() {
 		if (this.keyStoreType == null && this.sslProperties.getProperty(KEY_STORE_TYPE) == null) {
 			return KEY_STORE_DEFAULT_TYPE;
 		}
-		else if (this.keyStoreType != null) {
+		if (this.keyStoreType != null) {
 			return this.keyStoreType;
 		}
-		else {
-			return this.sslProperties.getProperty(KEY_STORE_TYPE);
-		}
+
+		return this.sslProperties.getProperty(KEY_STORE_TYPE);
 	}
 
 	/**
@@ -389,12 +389,11 @@ protected String getTrustStoreType() {
 		if (this.trustStoreType == null && this.sslProperties.getProperty(TRUST_STORE_TYPE) == null) {
 			return TRUST_STORE_DEFAULT_TYPE;
 		}
-		else if (this.trustStoreType != null) {
+		if (this.trustStoreType != null) {
 			return this.trustStoreType;
 		}
-		else {
-			return this.sslProperties.getProperty(TRUST_STORE_TYPE);
-		}
+
+		return this.sslProperties.getProperty(TRUST_STORE_TYPE);
 	}
 
 	/**
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitResourceHolder.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitResourceHolder.java
index 8cea93e04e..80fd02e75a 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitResourceHolder.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitResourceHolder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-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.
@@ -45,6 +45,7 @@
  * @author Mark Fisher
  * @author Dave Syer
  * @author Gary Russell
+ * @author Ngoc Nhan
  *
  * @see org.springframework.amqp.rabbit.transaction.RabbitTransactionManager
  * @see org.springframework.amqp.rabbit.core.RabbitTemplate
@@ -120,7 +121,7 @@ public final void addChannel(Channel channel, @Nullable Connection connection) {
 			if (connection != null) {
 				List channelsForConnection = this.channelsPerConnection.get(connection);
 				if (channelsForConnection == null) {
-					channelsForConnection = new LinkedList();
+					channelsForConnection = new LinkedList<>();
 					this.channelsPerConnection.put(connection, channelsForConnection);
 				}
 				channelsForConnection.add(channel);
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitUtils.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitUtils.java
index 4a958f6529..e135d365a2 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitUtils.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitUtils.java
@@ -228,12 +228,7 @@ public static void setPhysicalCloseRequired(Channel channel, boolean b) {
 	 */
 	public static boolean isPhysicalCloseRequired() {
 		Boolean mustClose = physicalCloseRequired.get();
-		if (mustClose == null) {
-			return false;
-		}
-		else {
-			return mustClose;
-		}
+		return mustClose != null && mustClose;
 	}
 
 	/**
@@ -322,13 +317,12 @@ public static boolean isMismatchedQueueArgs(Exception e) {
 		if (sig == null) {
 			return false;
 		}
-		else {
-			Method shutdownReason = sig.getReason();
-			return shutdownReason instanceof AMQP.Channel.Close closeReason
-					&& AMQP.PRECONDITION_FAILED == closeReason.getReplyCode()
-					&& closeReason.getClassId() == QUEUE_CLASS_ID_50
-					&& closeReason.getMethodId() == DECLARE_METHOD_ID_10;
-		}
+
+		Method shutdownReason = sig.getReason();
+		return shutdownReason instanceof AMQP.Channel.Close closeReason
+				&& AMQP.PRECONDITION_FAILED == closeReason.getReplyCode()
+				&& closeReason.getClassId() == QUEUE_CLASS_ID_50
+				&& closeReason.getMethodId() == DECLARE_METHOD_ID_10;
 	}
 
 	/**
@@ -352,13 +346,12 @@ public static boolean isExchangeDeclarationFailure(Exception e) {
 		if (sig == null) {
 			return false;
 		}
-		else {
-			Method shutdownReason = sig.getReason();
-			return shutdownReason instanceof AMQP.Connection.Close closeReason
-					&& AMQP.COMMAND_INVALID == closeReason.getReplyCode()
-					&& closeReason.getClassId() == EXCHANGE_CLASS_ID_40
-					&& closeReason.getMethodId() == DECLARE_METHOD_ID_10;
-		}
+
+		Method shutdownReason = sig.getReason();
+		return shutdownReason instanceof AMQP.Channel.Close closeReason
+				&& AMQP.PRECONDITION_FAILED == closeReason.getReplyCode()
+				&& closeReason.getClassId() == EXCHANGE_CLASS_ID_40
+				&& closeReason.getMethodId() == DECLARE_METHOD_ID_10;
 	}
 
 	/**
@@ -395,18 +388,13 @@ public static int getMaxFrame(ConnectionFactory connectionFactory) {
 	public static SaslConfig stringToSaslConfig(String saslConfig,
 			com.rabbitmq.client.ConnectionFactory connectionFactory) {
 
-		switch (saslConfig) {
-		case "DefaultSaslConfig.PLAIN":
-			return DefaultSaslConfig.PLAIN;
-		case "DefaultSaslConfig.EXTERNAL":
-			return DefaultSaslConfig.EXTERNAL;
-		case "JDKSaslConfig":
-			return new JDKSaslConfig(connectionFactory);
-		case "CRDemoSaslConfig":
-			return new CRDemoMechanism.CRDemoSaslConfig();
-		default:
-			throw new IllegalStateException("Unrecognized SaslConfig: " + saslConfig);
-		}
+		return switch (saslConfig) {
+			case "DefaultSaslConfig.PLAIN" -> DefaultSaslConfig.PLAIN;
+			case "DefaultSaslConfig.EXTERNAL" -> DefaultSaslConfig.EXTERNAL;
+			case "JDKSaslConfig" -> new JDKSaslConfig(connectionFactory);
+			case "CRDemoSaslConfig" -> new CRDemoMechanism.CRDemoSaslConfig();
+			default -> throw new IllegalStateException("Unrecognized SaslConfig: " + saslConfig);
+		};
 	}
 
 	/**
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/SimpleResourceHolder.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/SimpleResourceHolder.java
index bacdf4a61f..10036d3ad2 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/SimpleResourceHolder.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/SimpleResourceHolder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2019 the original author or authors.
+ * Copyright 2014-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.
@@ -45,6 +45,7 @@
  *
  * @author Artem Bilan
  * @author Gary Russell
+ * @author Ngoc Nhan
  * @since 1.3
  */
 public final class SimpleResourceHolder {
@@ -56,10 +57,10 @@ public final class SimpleResourceHolder {
 	private static final Log LOGGER = LogFactory.getLog(SimpleResourceHolder.class);
 
 	private static final ThreadLocal> RESOURCES =
-			new NamedThreadLocal>("Simple resources");
+			new NamedThreadLocal<>("Simple resources");
 
 	private static final ThreadLocal>> STACK =
-			new NamedThreadLocal>>("Simple resources");
+			new NamedThreadLocal<>("Simple resources");
 
 	/**
 	 * Return all resources that are bound to the current thread.
@@ -126,7 +127,7 @@ public static void bind(Object key, Object value) {
 		Map map = RESOURCES.get();
 		// set ThreadLocal Map if none found
 		if (map == null) {
-			map = new HashMap();
+			map = new HashMap<>();
 			RESOURCES.set(map);
 		}
 		Object oldValue = map.put(key, value);
@@ -175,7 +176,7 @@ public static Object pop(Object key) {
 		Map> stack = STACK.get();
 		if (stack != null) {
 			Deque deque = stack.get(key);
-			if (deque != null && deque.size() > 0) {
+			if (deque != null && !deque.isEmpty()) {
 				Object previousValue = deque.pop();
 				if (previousValue != null) {
 					bind(key, previousValue);
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ThreadChannelConnectionFactory.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ThreadChannelConnectionFactory.java
index d1b667532f..8097eed059 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ThreadChannelConnectionFactory.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ThreadChannelConnectionFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2023 the original author or authors.
+ * Copyright 2020-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.
@@ -24,7 +24,6 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
 
 import org.aopalliance.aop.Advice;
 import org.aopalliance.intercept.MethodInterceptor;
@@ -49,6 +48,7 @@
  * @author Gary Russell
  * @author Leonardo Ferreira
  * @author Christian Tzolov
+ * @author Ngoc Nhan
  * @since 2.3
  *
  */
@@ -191,12 +191,12 @@ public void destroy() {
 				this.connection.forceClose();
 				this.connection = null;
 			}
-			if (this.switchesInProgress.size() > 0 && this.logger.isWarnEnabled()) {
+			if (!this.switchesInProgress.isEmpty() && this.logger.isWarnEnabled()) {
 				this.logger.warn("Unclaimed context switches from threads:" +
 						this.switchesInProgress.values()
 								.stream()
-								.map(t -> t.getName())
-								.collect(Collectors.toList()));
+								.map(Thread::getName)
+								.toList());
 			}
 			this.contextSwitches.clear();
 			this.switchesInProgress.clear();
@@ -318,23 +318,21 @@ private Channel createProxy(Channel channel, boolean transactional) {
 			Advice advice =
 					(MethodInterceptor) invocation -> {
 						String method = invocation.getMethod().getName();
-						switch (method) {
-							case "close":
+						return switch (method) {
+							case "close" -> {
 								handleClose(channel, transactional);
-								return null;
-							case "getTargetChannel":
-								return channel;
-							case "isTransactional":
-								return transactional;
-							case "confirmSelect":
+								yield null;
+							}
+							case "getTargetChannel" -> channel;
+							case "isTransactional" -> transactional;
+							case "confirmSelect" -> {
 								confirmSelected.set(true);
-								return channel.confirmSelect();
-							case "isConfirmSelected":
-								return confirmSelected.get();
-							case "isPublisherConfirms":
-								return false;
-						}
-						return null;
+								yield channel.confirmSelect();
+							}
+							case "isConfirmSelected" -> confirmSelected.get();
+							case "isPublisherConfirms" -> false;
+							default -> null;
+						};
 					};
 			NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(advice);
 			advisor.addMethodName("close");
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/WebFluxNodeLocator.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/WebFluxNodeLocator.java
index 6c10164474..75acd71e81 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/WebFluxNodeLocator.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/WebFluxNodeLocator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 the original author or authors.
+ * Copyright 2022-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.
@@ -34,6 +34,7 @@
  * A {@link NodeLocator} using the Spring WebFlux {@link WebClient}.
  *
  * @author Gary Russell
+ * @author Ngoc Nhan
  * @since 2.4.8
  *
  */
@@ -46,14 +47,13 @@ public Map restCall(WebClient client, String baseUri, String vho
 
 		URI uri = new URI(baseUri)
 				.resolve("/api/queues/" + UriUtils.encodePathSegment(vhost, StandardCharsets.UTF_8) + "/" + queue);
-		HashMap queueInfo = client.get()
+		return client.get()
 				.uri(uri)
 				.accept(MediaType.APPLICATION_JSON)
 				.retrieve()
 				.bodyToMono(new ParameterizedTypeReference>() {
 				})
 				.block(Duration.ofSeconds(10)); // NOSONAR magic#
-		return queueInfo != null ? queueInfo : null;
 	}
 
 	/**
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/BatchingRabbitTemplate.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/BatchingRabbitTemplate.java
index 8982d80986..18e0b11719 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/BatchingRabbitTemplate.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/BatchingRabbitTemplate.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2023 the original author or authors.
+ * Copyright 2014-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.
@@ -99,7 +99,7 @@ public void send(String exchange, String routingKey, Message message,
 				}
 				Date next = this.batchingStrategy.nextRelease();
 				if (next != null) {
-					this.scheduledTask = this.scheduler.schedule((Runnable) () -> releaseBatches(), next.toInstant());
+					this.scheduledTask = this.scheduler.schedule(this::releaseBatches, next.toInstant());
 				}
 			}
 		}
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitAdmin.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitAdmin.java
index b86dc8063a..b85df8fece 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitAdmin.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitAdmin.java
@@ -81,6 +81,7 @@
  * @author Gary Russell
  * @author Artem Bilan
  * @author Christian Tzolov
+ * @author Ngoc Nhan
  */
 @ManagedResource(description = "Admin Tasks")
 public class RabbitAdmin implements AmqpAdmin, ApplicationContextAware, ApplicationEventPublisherAware,
@@ -648,8 +649,14 @@ public void afterPropertiesSet() {
 	@Override // NOSONAR complexity
 	public void initialize() {
 
+		redeclareBeanDeclarables();
 		redeclareManualDeclarables();
+	}
 
+	/**
+	 * Process bean declarables.
+	 */
+	private void redeclareBeanDeclarables() {
 		if (this.applicationContext == null) {
 			this.logger.debug("no ApplicationContext has been set, cannot auto-declare Exchanges, Queues, and Bindings");
 			return;
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java
index dc33e807d8..77315c4020 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java
@@ -159,6 +159,7 @@
  * @author Mohammad Hewedy
  * @author Alexey Platonov
  * @author Leonardo Ferreira
+ * @author Ngoc Nhan
  *
  * @since 1.0
  */
@@ -632,6 +633,18 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
 		this.evaluationContext.addPropertyAccessor(new MapAccessor());
 	}
 
+	/**
+	 * Return configured before post {@link MessagePostProcessor}s or {@code null}.
+	 * @return configured before post {@link MessagePostProcessor}s or {@code null}.
+	 * @since 3.2
+	 */
+	@Nullable
+	public Collection getBeforePublishPostProcessors() {
+		return this.beforePublishPostProcessors != null
+				? Collections.unmodifiableCollection(this.beforePublishPostProcessors)
+				: null;
+	}
+
 	/**
 	 * Set {@link MessagePostProcessor}s that will be invoked immediately before invoking
 	 * {@code Channel#basicPublish()}, after all other processing, except creating the
@@ -2486,7 +2499,7 @@ protected void observeTheSend(Channel channel, Message message, boolean mandator
 		ObservationRegistry registry = getObservationRegistry();
 		Observation observation = RabbitTemplateObservation.TEMPLATE_OBSERVATION.observation(this.observationConvention,
 				DefaultRabbitTemplateObservationConvention.INSTANCE,
-					() -> new RabbitMessageSenderContext(message, this.beanName, exch + "/" + rKey), registry);
+					() -> new RabbitMessageSenderContext(message, this.beanName, exch, rKey), registry);
 
 		observation.observe(() -> sendToRabbit(channel, exch, rKey, mandatory, message));
 	}
@@ -2820,7 +2833,7 @@ public void handleDelivery(String consumerTag, Envelope envelope, BasicPropertie
 		return consumer;
 	}
 
-	private static class PendingReply {
+	private static final class PendingReply {
 
 		@Nullable
 		private volatile String savedReplyTo;
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractMessageListenerContainer.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractMessageListenerContainer.java
index 7c3b44618f..80df1dab3d 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractMessageListenerContainer.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractMessageListenerContainer.java
@@ -1490,7 +1490,7 @@ protected void invokeErrorHandler(Throwable ex) {
 	// -------------------------------------------------------------------------
 
 	/**
-	 * Execute the specified listener, committing or rolling back the transaction afterwards (if necessary).
+	 * Execute the specified listener, committing or rolling back the transaction afterward (if necessary).
 	 * @param channel the Rabbit Channel to operate on
 	 * @param data the received Rabbit Message
 	 * @see #invokeListener
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractRabbitListenerEndpoint.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractRabbitListenerEndpoint.java
index a4031e88dc..95bcaffc40 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractRabbitListenerEndpoint.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractRabbitListenerEndpoint.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2022 the original author or authors.
+ * Copyright 2014-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.
@@ -46,6 +46,7 @@
  * @author Stephane Nicoll
  * @author Gary Russell
  * @author Artem Bilan
+ * @author Ngoc Nhan
  *
  * @since 1.4
  *
@@ -298,7 +299,7 @@ public void setTaskExecutor(TaskExecutor taskExecutor) {
 	 * @return true if batch.
 	 */
 	public boolean isBatchListener() {
-		return this.batchListener == null ? false : this.batchListener;
+		return this.batchListener != null && this.batchListener;
 	}
 
 	@Override
@@ -396,8 +397,7 @@ public void setupListenerContainer(MessageListenerContainer listenerContainer) {
 				throw new IllegalStateException("Queues or queue names must be provided but not both for " + this);
 			}
 			if (queuesEmpty) {
-				Collection names = qNames;
-				container.setQueueNames(names.toArray(new String[0]));
+				container.setQueueNames(qNames.toArray(new String[0]));
 			}
 			else {
 				Collection instances = getQueues();
@@ -426,7 +426,7 @@ public void setupListenerContainer(MessageListenerContainer listenerContainer) {
 	 * Create a {@link MessageListener} that is able to serve this endpoint for the
 	 * specified container.
 	 * @param container the {@link MessageListenerContainer} to create a {@link MessageListener}.
-	 * @return a a {@link MessageListener} instance.
+	 * @return a {@link MessageListener} instance.
 	 */
 	protected abstract MessageListener createMessageListener(MessageListenerContainer container);
 
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/ConditionalRejectingErrorHandler.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/ConditionalRejectingErrorHandler.java
index acd7cbedb8..33204d1b64 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/ConditionalRejectingErrorHandler.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/ConditionalRejectingErrorHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2023 the original author or authors.
+ * Copyright 2014-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.
@@ -50,6 +50,7 @@
  * {@link AmqpRejectAndDontRequeueException}.
  *
  * @author Gary Russell
+ * @author Ngoc Nhan
  * @since 1.3.2
  *
  */
@@ -136,7 +137,7 @@ public void handleError(Throwable t) {
 				Message failed = lefe.getFailedMessage();
 				if (failed != null) {
 					List> xDeath = failed.getMessageProperties().getXDeathHeader();
-					if (xDeath != null && xDeath.size() > 0) {
+					if (xDeath != null && !xDeath.isEmpty()) {
 						this.logger.error("x-death header detected on a message with a fatal exception; "
 								+ "perhaps requeued from a DLQ? - discarding: " + failed);
 						handleDiscarded(failed);
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/MicrometerHolder.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/MicrometerHolder.java
index 72d06e140f..5cd536329f 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/MicrometerHolder.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/MicrometerHolder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 the original author or authors.
+ * Copyright 2022-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.
@@ -33,6 +33,7 @@
  * Abstraction to avoid hard reference to Micrometer.
  *
  * @author Gary Russell
+ * @author Ngoc Nhan
  * @since 2.4.6
  *
  */
@@ -95,7 +96,7 @@ private Timer buildTimer(String aListenerId, String result, String queue, String
 				.tag("result", result)
 				.tag("exception", exception);
 		if (this.tags != null && !this.tags.isEmpty()) {
-			this.tags.forEach((key, value) -> builder.tag(key, value));
+			this.tags.forEach(builder::tag);
 		}
 		Timer registeredTimer = builder.register(this.registry);
 		this.timers.put(queue + exception, registeredTimer);
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/MultiMethodRabbitListenerEndpoint.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/MultiMethodRabbitListenerEndpoint.java
index 2f5ebbd865..1833eee771 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/MultiMethodRabbitListenerEndpoint.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/MultiMethodRabbitListenerEndpoint.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015-2022 the original author or authors.
+ * Copyright 2015-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.
@@ -29,6 +29,7 @@
 
 /**
  * @author Gary Russell
+ * @author Ngoc Nhan
  * @since 1.5
  *
  */
@@ -64,7 +65,7 @@ public void setValidator(Validator validator) {
 
 	@Override
 	protected HandlerAdapter configureListenerAdapter(MessagingMessageListenerAdapter messageListener) {
-		List invocableHandlerMethods = new ArrayList();
+		List invocableHandlerMethods = new ArrayList<>();
 		InvocableHandlerMethod defaultHandler = null;
 		for (Method method : this.methods) {
 			InvocableHandlerMethod handler = getMessageHandlerMethodFactory()
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/RabbitListenerContainerFactory.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/RabbitListenerContainerFactory.java
index ebb7b82832..4b1b1ae320 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/RabbitListenerContainerFactory.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/RabbitListenerContainerFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-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.
@@ -16,6 +16,7 @@
 
 package org.springframework.amqp.rabbit.listener;
 
+import org.springframework.beans.factory.BeanNameAware;
 import org.springframework.lang.Nullable;
 
 /**
@@ -23,11 +24,12 @@
  * @param  the container type.
  * @author Stephane Nicoll
  * @author Gary Russell
+ * @author Ngoc Nhan
  * @since 1.4
  * @see RabbitListenerEndpoint
  */
 @FunctionalInterface
-public interface RabbitListenerContainerFactory {
+public interface RabbitListenerContainerFactory extends BeanNameAware {
 
 	/**
 	 * Create a {@link MessageListenerContainer} for the given
@@ -48,4 +50,19 @@ default C createListenerContainer() {
 		return createListenerContainer(null);
 	}
 
+	@Override
+	default void setBeanName(String name) {
+
+	}
+
+	/**
+	 * Return a bean name of the component or null if not a bean.
+	 * @return the bean name.
+	 * @since 3.2
+	 */
+	@Nullable
+	default String getBeanName() {
+		return null;
+	}
+
 }
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/MessageListenerAdapter.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/MessageListenerAdapter.java
index c9c90a1871..b101a33954 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/MessageListenerAdapter.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/MessageListenerAdapter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-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.
@@ -89,7 +89,7 @@
  *
  * This next example illustrates a Message delegate that just consumes the String contents of
  * {@link Message Messages}. Notice also how the name of the Message handling method is different from the
- * {@link #ORIGINAL_DEFAULT_LISTENER_METHOD original} (this will have to be configured in the attandant bean
+ * {@link #ORIGINAL_DEFAULT_LISTENER_METHOD original} (this will have to be configured in the attendant bean
  * definition). Again, no Message will be sent back as the method returns void.
  *
  * 
@@ -118,6 +118,7 @@
  * @author Gary Russell
  * @author Greg Turnquist
  * @author Cai Kun
+ * @author Ngoc Nhan
  *
  * @see #setDelegate
  * @see #setDefaultListenerMethod
@@ -129,7 +130,7 @@
  */
 public class MessageListenerAdapter extends AbstractAdaptableMessageListener {
 
-	private final Map queueOrTagToMethodName = new HashMap();
+	private final Map queueOrTagToMethodName = new HashMap<>();
 
 	/**
 	 * Out-of-the-box value for the default listener method: "handleMessage".
@@ -314,7 +315,7 @@ else if (delegateListener instanceof MessageListener messageListener) {
 	 * @see #setQueueOrTagToMethodName
 	 */
 	protected String getListenerMethodName(Message originalMessage, Object extractedMessage) {
-		if (this.queueOrTagToMethodName.size() > 0) {
+		if (!this.queueOrTagToMethodName.isEmpty()) {
 			MessageProperties props = originalMessage.getMessageProperties();
 			String methodName = this.queueOrTagToMethodName.get(props.getConsumerQueue());
 			if (methodName == null) {
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/MessagingMessageListenerAdapter.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/MessagingMessageListenerAdapter.java
index 47ffbed9ee..88313d0bd2 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/MessagingMessageListenerAdapter.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/MessagingMessageListenerAdapter.java
@@ -41,6 +41,7 @@
 import org.springframework.messaging.handler.annotation.Headers;
 import org.springframework.messaging.handler.annotation.Payload;
 import org.springframework.util.Assert;
+import org.springframework.util.TypeUtils;
 
 import com.rabbitmq.client.Channel;
 
@@ -456,17 +457,18 @@ private Type extractGenericParameterTypFromMethodParameter(MethodParameter metho
 				if (parameterizedType.getRawType().equals(Message.class)) {
 					genericParameterType = ((ParameterizedType) genericParameterType).getActualTypeArguments()[0];
 				}
-				else if (this.isBatch
-						&& ((parameterizedType.getRawType().equals(List.class)
-						|| parameterizedType.getRawType().equals(Collection.class))
-						&& parameterizedType.getActualTypeArguments().length == 1)) {
+				else if (this.isBatch &&
+						(parameterizedType.getRawType().equals(List.class) ||
+								(parameterizedType.getRawType().equals(Collection.class) &&
+										parameterizedType.getActualTypeArguments().length == 1))) {
 
 					this.isCollection = true;
 					Type paramType = parameterizedType.getActualTypeArguments()[0];
 					boolean messageHasGeneric = paramType instanceof ParameterizedType pType
 							&& pType.getRawType().equals(Message.class);
-					this.isMessageList = paramType.equals(Message.class) || messageHasGeneric;
-					this.isAmqpMessageList = paramType.equals(org.springframework.amqp.core.Message.class);
+					this.isMessageList = TypeUtils.isAssignable(paramType, Message.class) || messageHasGeneric;
+					this.isAmqpMessageList =
+							TypeUtils.isAssignable(paramType, org.springframework.amqp.core.Message.class);
 					if (messageHasGeneric) {
 						genericParameterType = ((ParameterizedType) paramType).getActualTypeArguments()[0];
 					}
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/retry/RepublishMessageRecoverer.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/retry/RepublishMessageRecoverer.java
index ae84767aa3..61bb2122c7 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/retry/RepublishMessageRecoverer.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/retry/RepublishMessageRecoverer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2022 the original author or authors.
+ * Copyright 2014-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.
@@ -91,7 +91,7 @@ public class RepublishMessageRecoverer implements MessageRecoverer {
 	 * @param errorTemplate the template.
 	 */
 	public RepublishMessageRecoverer(AmqpTemplate errorTemplate) {
-		this(errorTemplate, (String) null, (String) null);
+		this(errorTemplate, null, (String) null);
 	}
 
 	/**
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/ActiveObjectCounter.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/ActiveObjectCounter.java
index 30abbced57..e37a5653be 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/ActiveObjectCounter.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/ActiveObjectCounter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-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.
@@ -29,11 +29,12 @@
  *
  * @author Dave Syer
  * @author Artem Bilan
+ * @author Ngoc Nhan
  *
  */
 public class ActiveObjectCounter {
 
-	private final ConcurrentMap locks = new ConcurrentHashMap();
+	private final ConcurrentMap locks = new ConcurrentHashMap<>();
 
 	private volatile boolean active = true;
 
@@ -56,7 +57,7 @@ public boolean await(long timeout, TimeUnit timeUnit) throws InterruptedExceptio
 			if (this.locks.isEmpty()) {
 				return true;
 			}
-			Collection objects = new HashSet(this.locks.keySet());
+			Collection objects = new HashSet<>(this.locks.keySet());
 			for (T object : objects) {
 				CountDownLatch lock = this.locks.get(object);
 				if (lock == null) {
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/DefaultMessagePropertiesConverter.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/DefaultMessagePropertiesConverter.java
index 489107b7aa..44ccbdfe0e 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/DefaultMessagePropertiesConverter.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/DefaultMessagePropertiesConverter.java
@@ -42,6 +42,7 @@
  * @author Soeren Unruh
  * @author Raylax Grey
  * @author Artem Bilan
+ * @author Ngoc Nhan
  *
  * @since 1.0
  */
@@ -100,6 +101,12 @@ public MessageProperties toMessageProperties(BasicProperties source, @Nullable E
 						target.setHeader(key, receivedDelayLongValue);
 					}
 				}
+				else if (MessageProperties.RETRY_COUNT.equals(key)) {
+					Object value = entry.getValue();
+					if (value instanceof Number numberValue) {
+						target.setRetryCount(numberValue.longValue());
+					}
+				}
 				else {
 					target.setHeader(key, convertLongStringIfNecessary(entry.getValue(), charset));
 				}
@@ -134,13 +141,26 @@ public MessageProperties toMessageProperties(BasicProperties source, @Nullable E
 			target.setRedelivered(envelope.isRedeliver());
 			target.setDeliveryTag(envelope.getDeliveryTag());
 		}
+
+		if (target.getRetryCount() == 0) {
+			List> xDeathHeader = target.getXDeathHeader();
+			if (!CollectionUtils.isEmpty(xDeathHeader)) {
+				target.setRetryCount((long) xDeathHeader.get(0).get("count"));
+			}
+		}
+
 		return target;
 	}
 
 	@Override
 	public BasicProperties fromMessageProperties(final MessageProperties source, final String charset) {
 		BasicProperties.Builder target = new BasicProperties.Builder();
-		target.headers(this.convertHeadersIfNecessary(source.getHeaders()))
+		Map headers = convertHeadersIfNecessary(source.getHeaders());
+		long retryCount = source.getRetryCount();
+		if (retryCount > 0) {
+			headers.put(MessageProperties.RETRY_COUNT, retryCount);
+		}
+		target.headers(headers)
 			.timestamp(source.getTimestamp())
 			.messageId(source.getMessageId())
 			.userId(source.getUserId())
@@ -256,27 +276,24 @@ private Object convertLongString(LongString longString, String charset) {
 	 * @return the converted string.
 	 */
 	private Object convertLongStringIfNecessary(Object valueArg, String charset) {
-		Object value = valueArg;
-		if (value instanceof LongString longStr) {
-			value = convertLongString(longStr, charset);
+		if (valueArg instanceof LongString longStr) {
+			return convertLongString(longStr, charset);
 		}
-		else if (value instanceof List) {
-			List convertedList = new ArrayList<>(((List) value).size());
-			for (Object listValue : (List) value) {
-				convertedList.add(this.convertLongStringIfNecessary(listValue, charset));
-			}
-			value = convertedList;
+
+		if (valueArg instanceof List values) {
+			List convertedList = new ArrayList<>(values.size());
+			values.forEach(value -> convertedList.add(this.convertLongStringIfNecessary(value, charset)));
+			return convertedList;
 		}
-		else if (value instanceof Map) {
-			@SuppressWarnings("unchecked")
-			Map originalMap = (Map) value;
+
+		if (valueArg instanceof Map originalMap) {
 			Map convertedMap = new HashMap<>();
-			for (Map.Entry entry : originalMap.entrySet()) {
-				convertedMap.put(entry.getKey(), this.convertLongStringIfNecessary(entry.getValue(), charset));
-			}
-			value = convertedMap;
+			originalMap.forEach(
+				(key, value) -> convertedMap.put((String) key, this.convertLongStringIfNecessary(value, charset)));
+			return convertedMap;
 		}
-		return value;
+
+		return valueArg;
 	}
 
 }
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/micrometer/RabbitListenerObservation.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/micrometer/RabbitListenerObservation.java
index b580bd2e7e..19e75baf0b 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/micrometer/RabbitListenerObservation.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/micrometer/RabbitListenerObservation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 the original author or authors.
+ * Copyright 2022-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.
@@ -26,8 +26,10 @@
  * Spring Rabbit Observation for listeners.
  *
  * @author Gary Russell
- * @since 3.0
+ * @author Vincent Meunier
+ * @author Artem Bilan
  *
+ * @since 3.0
  */
 public enum RabbitListenerObservation implements ObservationDocumentation {
 
@@ -36,20 +38,19 @@ public enum RabbitListenerObservation implements ObservationDocumentation {
 	 */
 	LISTENER_OBSERVATION {
 
-
 		@Override
 		public Class> getDefaultConvention() {
 			return DefaultRabbitListenerObservationConvention.class;
 		}
 
 		@Override
-		public String getPrefix() {
-			return "spring.rabbit.listener";
+		public KeyName[] getLowCardinalityKeyNames() {
+			return ListenerLowCardinalityTags.values();
 		}
 
 		@Override
-		public KeyName[] getLowCardinalityKeyNames() {
-			return ListenerLowCardinalityTags.values();
+		public KeyName[] getHighCardinalityKeyNames() {
+			return ListenerHighCardinalityTags.values();
 		}
 
 	};
@@ -69,10 +70,64 @@ public String asString() {
 				return "spring.rabbit.listener.id";
 			}
 
+		},
+
+		/**
+		 * The queue the listener is plugged to.
+		 *
+		 * @since 3.2
+		 */
+		DESTINATION_NAME {
+
+			@Override
+			public String asString() {
+				return "messaging.destination.name";
+			}
+
+		},
+
+		/**
+		 * The delivery tag.
+		 * After deprecation this key is not exposed as a low cardinality tag.
+		 *
+		 * @since 3.2
+		 *
+		 * @deprecated in favor of {@link ListenerHighCardinalityTags#DELIVERY_TAG}
+		 */
+		@Deprecated(since = "3.2.1", forRemoval = true)
+		DELIVERY_TAG {
+
+			@Override
+			public String asString() {
+				return "messaging.rabbitmq.message.delivery_tag";
+			}
+
+		}
+
+	}
+
+	/**
+	 * High cardinality tags.
+	 *
+	 * @since 3.2.1
+	 */
+	public enum ListenerHighCardinalityTags implements KeyName {
+
+		/**
+		 * The delivery tag.
+		 */
+		DELIVERY_TAG {
+
+			@Override
+			public String asString() {
+				return "messaging.rabbitmq.message.delivery_tag";
+			}
+
 		}
 
 	}
 
+
 	/**
 	 * Default {@link RabbitListenerObservationConvention} for Rabbit listener key values.
 	 */
@@ -86,8 +141,18 @@ public static class DefaultRabbitListenerObservationConvention implements Rabbit
 
 		@Override
 		public KeyValues getLowCardinalityKeyValues(RabbitMessageReceiverContext context) {
-			return KeyValues.of(RabbitListenerObservation.ListenerLowCardinalityTags.LISTENER_ID.asString(),
-							context.getListenerId());
+			final var messageProperties = context.getCarrier().getMessageProperties();
+			return KeyValues.of(
+					RabbitListenerObservation.ListenerLowCardinalityTags.LISTENER_ID.asString(),
+					context.getListenerId(),
+					RabbitListenerObservation.ListenerLowCardinalityTags.DESTINATION_NAME.asString(),
+					messageProperties.getConsumerQueue());
+		}
+
+		@Override
+		public KeyValues getHighCardinalityKeyValues(RabbitMessageReceiverContext context) {
+			return KeyValues.of(RabbitListenerObservation.ListenerHighCardinalityTags.DELIVERY_TAG.asString(),
+					String.valueOf(context.getCarrier().getMessageProperties().getDeliveryTag()));
 		}
 
 		@Override
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/micrometer/RabbitMessageSenderContext.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/micrometer/RabbitMessageSenderContext.java
index e327f6ebc6..b7994ad645 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/micrometer/RabbitMessageSenderContext.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/micrometer/RabbitMessageSenderContext.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 the original author or authors.
+ * Copyright 2022-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.
@@ -24,6 +24,7 @@
  * {@link SenderContext} for {@link Message}s.
  *
  * @author Gary Russell
+ * @author Ngoc Nhan
  * @since 3.0
  *
  */
@@ -33,14 +34,40 @@ public class RabbitMessageSenderContext extends SenderContext {
 
 	private final String destination;
 
+	private final String exchange;
+
+	private final String routingKey;
+
+	@Deprecated(since = "3.2")
 	public RabbitMessageSenderContext(Message message, String beanName, String destination) {
 		super((carrier, key, value) -> message.getMessageProperties().setHeader(key, value));
 		setCarrier(message);
 		this.beanName = beanName;
+		this.exchange = null;
+		this.routingKey = null;
 		this.destination = destination;
 		setRemoteServiceName("RabbitMQ");
 	}
 
+
+	/**
+	 * Create an instance {@code RabbitMessageSenderContext}.
+	 * @param message a message to send
+	 * @param beanName the bean name
+	 * @param exchange the name of the exchange
+	 * @param routingKey the routing key
+	 * @since 3.2
+	 */
+	public RabbitMessageSenderContext(Message message, String beanName, String exchange, String routingKey) {
+		super((carrier, key, value) -> message.getMessageProperties().setHeader(key, value));
+		setCarrier(message);
+		this.beanName = beanName;
+		this.exchange = exchange;
+		this.routingKey = routingKey;
+		this.destination = exchange + "/" + routingKey;
+		setRemoteServiceName("RabbitMQ");
+	}
+
 	public String getBeanName() {
 		return this.beanName;
 	}
@@ -53,4 +80,22 @@ public String getDestination() {
 		return this.destination;
 	}
 
+	/**
+	 * Return the exchange.
+	 * @return the exchange.
+	 * @since 3.2
+	 */
+	public String getExchange() {
+		return this.exchange;
+	}
+
+	/**
+	 * Return the routingKey.
+	 * @return the routingKey.
+	 * @since 3.2
+	 */
+	public String getRoutingKey() {
+		return this.routingKey;
+	}
+
 }
diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/micrometer/RabbitTemplateObservation.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/micrometer/RabbitTemplateObservation.java
index f3bc17f1e6..06a5551878 100644
--- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/micrometer/RabbitTemplateObservation.java
+++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/micrometer/RabbitTemplateObservation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 the original author or authors.
+ * Copyright 2022-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.
@@ -26,6 +26,9 @@
  * Spring RabbitMQ Observation for {@link org.springframework.amqp.rabbit.core.RabbitTemplate}.
  *
  * @author Gary Russell
+ * @author Vincent Meunier
+ * @author Artem Bilan
+ *
  * @since 3.0
  *
  */
@@ -41,11 +44,6 @@ public Class> getDefaultConve
 			return DefaultRabbitTemplateObservationConvention.class;
 		}
 
-		@Override
-		public String getPrefix() {
-			return "spring.rabbit.template";
-		}
-
 		@Override
 		public KeyName[] getLowCardinalityKeyNames() {
 			return TemplateLowCardinalityTags.values();
@@ -68,8 +66,35 @@ public String asString() {
 				return "spring.rabbit.template.name";
 			}
 
+		},
+
+		/**
+		 * The destination exchange (empty if default exchange).
+		 * @since 3.2
+		 */
+		EXCHANGE {
+
+			@Override
+			public String asString() {
+				return "messaging.destination.name";
+			}
+
+		},
+
+		/**
+		 * The destination routing key.
+		 * @since 3.2
+		 */
+		ROUTING_KEY {
+
+			@Override
+			public String asString() {
+				return "messaging.rabbitmq.destination.routing_key";
+			}
+
 		}
 
+
 	}
 
 	/**
@@ -85,8 +110,11 @@ public static class DefaultRabbitTemplateObservationConvention implements Rabbit
 
 		@Override
 		public KeyValues getLowCardinalityKeyValues(RabbitMessageSenderContext context) {
-			return KeyValues.of(RabbitTemplateObservation.TemplateLowCardinalityTags.BEAN_NAME.asString(),
-							context.getBeanName());
+			return KeyValues.of(
+					TemplateLowCardinalityTags.BEAN_NAME.asString(), context.getBeanName(),
+					TemplateLowCardinalityTags.EXCHANGE.asString(), context.getExchange(),
+					TemplateLowCardinalityTags.ROUTING_KEY.asString(), context.getRoutingKey()
+			);
 		}
 
 		@Override
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/AsyncRabbitTemplateTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/AsyncRabbitTemplateTests.java
index aadf18d773..f11b0c670e 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/AsyncRabbitTemplateTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/AsyncRabbitTemplateTests.java
@@ -22,6 +22,7 @@
 import static org.awaitility.Awaitility.await;
 import static org.mockito.Mockito.mock;
 
+import java.time.Duration;
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.CancellationException;
@@ -35,6 +36,8 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BiConsumer;
 
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 import org.springframework.amqp.core.Address;
@@ -92,6 +95,11 @@ public class AsyncRabbitTemplateTests {
 
 	private final Message fooMessage = new SimpleMessageConverter().toMessage("foo", new MessageProperties());
 
+	@BeforeAll
+	static void setup() {
+		Awaitility.setDefaultTimeout(Duration.ofSeconds(30));
+	}
+
 	@Test
 	public void testConvert1Arg() throws Exception {
 		final AtomicBoolean mppCalled = new AtomicBoolean();
@@ -502,7 +510,8 @@ private Message checkMessageResult(CompletableFuture future, String exp
 		});
 		assertThat(cdl.await(10, TimeUnit.SECONDS)).isTrue();
 		assertThat(new String(resultRef.get().getBody())).isEqualTo(expected);
-		assertThat(TestUtils.getPropertyValue(future, "timeoutTask", Future.class).isCancelled()).isTrue();
+		await().untilAsserted(() ->
+				assertThat(TestUtils.getPropertyValue(future, "timeoutTask", Future.class).isCancelled()).isTrue());
 		return resultRef.get();
 	}
 
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/AbstractRabbitAnnotationDrivenTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/AbstractRabbitAnnotationDrivenTests.java
index 271731d2af..bdfb6abbca 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/AbstractRabbitAnnotationDrivenTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/AbstractRabbitAnnotationDrivenTests.java
@@ -48,6 +48,7 @@
  *
  * @author Stephane Nicoll
  * @author Gary Russell
+ * @author Ngoc Nhan
  */
 public abstract class AbstractRabbitAnnotationDrivenTests {
 
@@ -87,7 +88,9 @@ public void testSampleConfiguration(ApplicationContext context, int expectedDefa
 				context.getBean("rabbitListenerContainerFactory", RabbitListenerContainerTestFactory.class);
 		RabbitListenerContainerTestFactory simpleFactory =
 				context.getBean("simpleFactory", RabbitListenerContainerTestFactory.class);
+		assertThat(defaultFactory.getBeanName()).isEqualTo("rabbitListenerContainerFactory");
 		assertThat(defaultFactory.getListenerContainers()).hasSize(expectedDefaultContainers);
+		assertThat(simpleFactory.getBeanName()).isEqualTo("simpleFactory");
 		assertThat(simpleFactory.getListenerContainers()).hasSize(1);
 		Map queues = context
 				.getBeansOfType(org.springframework.amqp.core.Queue.class);
@@ -129,6 +132,7 @@ private void checkAdmin(Collection admins) {
 	public void testFullConfiguration(ApplicationContext context) {
 		RabbitListenerContainerTestFactory simpleFactory =
 				context.getBean("simpleFactory", RabbitListenerContainerTestFactory.class);
+		assertThat(simpleFactory.getBeanName()).isEqualTo("simpleFactory");
 		assertThat(simpleFactory.getListenerContainers()).hasSize(1);
 		MethodRabbitListenerEndpoint endpoint = (MethodRabbitListenerEndpoint)
 				simpleFactory.getListenerContainers().get(0).getEndpoint();
@@ -168,7 +172,9 @@ public void testCustomConfiguration(ApplicationContext context) {
 				context.getBean("rabbitListenerContainerFactory", RabbitListenerContainerTestFactory.class);
 		RabbitListenerContainerTestFactory customFactory =
 				context.getBean("customFactory", RabbitListenerContainerTestFactory.class);
+		assertThat(defaultFactory.getBeanName()).isEqualTo("rabbitListenerContainerFactory");
 		assertThat(defaultFactory.getListenerContainers()).hasSize(1);
+		assertThat(customFactory.getBeanName()).isEqualTo("customFactory");
 		assertThat(customFactory.getListenerContainers()).hasSize(1);
 		RabbitListenerEndpoint endpoint = defaultFactory.getListenerContainers().get(0).getEndpoint();
 		assertThat(endpoint.getClass()).as("Wrong endpoint type").isEqualTo(SimpleRabbitListenerEndpoint.class);
@@ -191,6 +197,7 @@ public void testCustomConfiguration(ApplicationContext context) {
 	public void testExplicitContainerFactoryConfiguration(ApplicationContext context) {
 		RabbitListenerContainerTestFactory defaultFactory =
 				context.getBean("simpleFactory", RabbitListenerContainerTestFactory.class);
+		assertThat(defaultFactory.getBeanName()).isEqualTo("simpleFactory");
 		assertThat(defaultFactory.getListenerContainers()).hasSize(1);
 	}
 
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/ContentTypeDelegatingMessageConverterIntegrationTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/ContentTypeDelegatingMessageConverterIntegrationTests.java
index 91a23421ba..3ac031bcd0 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/ContentTypeDelegatingMessageConverterIntegrationTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/ContentTypeDelegatingMessageConverterIntegrationTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-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.
@@ -44,6 +44,7 @@
 import org.springframework.messaging.MessageHeaders;
 import org.springframework.messaging.handler.annotation.SendTo;
 import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
 import org.springframework.util.MimeType;
 
@@ -54,6 +55,7 @@
  */
 @RabbitAvailable
 @SpringJUnitConfig
+@DirtiesContext
 public class ContentTypeDelegatingMessageConverterIntegrationTests {
 
 	@Autowired
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/OptionalPayloadTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/OptionalPayloadTests.java
index 1d5a476433..56790f4eab 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/OptionalPayloadTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/OptionalPayloadTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 the original author or authors.
+ * Copyright 2022-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.
@@ -40,6 +40,7 @@
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.messaging.handler.annotation.Payload;
+import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -52,6 +53,7 @@
  */
 @SpringJUnitConfig
 @RabbitAvailable(queues = { "op.1", "op.2" })
+@DirtiesContext
 public class OptionalPayloadTests {
 
 	@Test
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/RabbitListenerContainerTestFactory.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/RabbitListenerContainerTestFactory.java
index fa592274fb..9e68785e27 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/RabbitListenerContainerTestFactory.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/RabbitListenerContainerTestFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2019 the original author or authors.
+ * Copyright 2014-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.
@@ -40,6 +40,8 @@ public class RabbitListenerContainerTestFactory implements RabbitListenerContain
 	private final Map listenerContainers =
 			new LinkedHashMap();
 
+	private String beanName;
+
 	public List getListenerContainers() {
 		return new ArrayList(this.listenerContainers.values());
 	}
@@ -63,4 +65,13 @@ public MessageListenerTestContainer createListenerContainer(RabbitListenerEndpoi
 		return container;
 	}
 
+	@Override
+	public void setBeanName(String name) {
+		this.beanName = name;
+	}
+
+	public String getBeanName() {
+		return this.beanName;
+	}
+
 }
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/AbstractConnectionFactoryTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/AbstractConnectionFactoryTests.java
index 58546447c8..097ddf9632 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/AbstractConnectionFactoryTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/AbstractConnectionFactoryTests.java
@@ -20,6 +20,7 @@
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.BDDMockito.willCallRealMethod;
@@ -32,6 +33,7 @@
 import static org.mockito.Mockito.verify;
 
 import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -64,11 +66,11 @@ public abstract class AbstractConnectionFactoryTests {
 
 	@Test
 	public void testWithListener() throws Exception {
-
-		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class);
-		com.rabbitmq.client.Connection mockConnection = mock(com.rabbitmq.client.Connection.class);
+		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock();
+		com.rabbitmq.client.Connection mockConnection = mock();
 
 		given(mockConnectionFactory.newConnection(any(ExecutorService.class), anyString())).willReturn(mockConnection);
+		given(mockConnectionFactory.newConnection(any(), anyList(), anyString())).willReturn(mockConnection);
 
 		final AtomicInteger called = new AtomicInteger(0);
 		AbstractConnectionFactory connectionFactory = createConnectionFactory(mockConnectionFactory);
@@ -109,7 +111,7 @@ public void onClose(Connection connection) {
 
 		verify(mockConnectionFactory, times(1)).newConnection(any(ExecutorService.class), anyString());
 
-		connectionFactory.setAddresses("foo:5672,bar:5672");
+		connectionFactory.setAddresses(List.of("foo:5672", "bar:5672"));
 		connectionFactory.setAddressShuffleMode(AddressShuffleMode.NONE);
 		con = connectionFactory.createConnection();
 		assertThat(called.get()).isEqualTo(1);
@@ -125,9 +127,8 @@ public void onClose(Connection connection) {
 
 	@Test
 	public void testWithListenerRegisteredAfterOpen() throws Exception {
-
-		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class);
-		com.rabbitmq.client.Connection mockConnection = mock(com.rabbitmq.client.Connection.class);
+		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock();
+		com.rabbitmq.client.Connection mockConnection = mock();
 
 		given(mockConnectionFactory.newConnection(any(ExecutorService.class), anyString())).willReturn(mockConnection);
 
@@ -168,10 +169,9 @@ public void onClose(Connection connection) {
 
 	@Test
 	public void testCloseInvalidConnection() throws Exception {
-
-		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class);
-		com.rabbitmq.client.Connection mockConnection1 = mock(com.rabbitmq.client.Connection.class);
-		com.rabbitmq.client.Connection mockConnection2 = mock(com.rabbitmq.client.Connection.class);
+		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock();
+		com.rabbitmq.client.Connection mockConnection1 = mock();
+		com.rabbitmq.client.Connection mockConnection2 = mock();
 
 		given(mockConnectionFactory.newConnection(any(ExecutorService.class), anyString()))
 				.willReturn(mockConnection1, mockConnection2);
@@ -194,8 +194,7 @@ public void testCloseInvalidConnection() throws Exception {
 
 	@Test
 	public void testDestroyBeforeUsed() throws Exception {
-
-		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class);
+		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock();
 
 		AbstractConnectionFactory connectionFactory = createConnectionFactory(mockConnectionFactory);
 		connectionFactory.destroy();
@@ -205,7 +204,7 @@ public void testDestroyBeforeUsed() throws Exception {
 
 	@Test
 	public void testCreatesConnectionWithGivenFactory() {
-		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class);
+		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock();
 		willCallRealMethod().given(mockConnectionFactory).params(any(ExecutorService.class));
 		willCallRealMethod().given(mockConnectionFactory).setThreadFactory(any(ThreadFactory.class));
 		willCallRealMethod().given(mockConnectionFactory).getThreadFactory();
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactoryTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactoryTests.java
index 66dc8013ec..8ecddfcb40 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactoryTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactoryTests.java
@@ -22,6 +22,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -117,7 +118,7 @@ void stringRepresentation() {
 		assertThat(ccf.toString()).contains(", addresses=[h3:1236, h4:1237]")
 				.doesNotContain("host")
 				.doesNotContain("port");
-		ccf.setAddressResolver(() ->  {
+		ccf.setAddressResolver(() -> {
 			throw new IOException("test");
 		});
 		ccf.setPort(0);
@@ -710,7 +711,7 @@ public void testCheckoutLimitWithPublisherConfirmsLogicalAlreadyCloses() throws
 		willAnswer(invoc -> {
 			open.set(false); // so the logical close detects a closed delegate
 			return null;
-		}).given(mockChannel).basicPublish(any(), any(), anyBoolean(),  any(), any());
+		}).given(mockChannel).basicPublish(any(), any(), anyBoolean(), any(), any());
 
 		CachingConnectionFactory ccf = new CachingConnectionFactory(mockConnectionFactory);
 		ccf.setExecutor(mock(ExecutorService.class));
@@ -722,7 +723,7 @@ public void testCheckoutLimitWithPublisherConfirmsLogicalAlreadyCloses() throws
 		rabbitTemplate.convertAndSend("foo", "bar");
 		open.set(true);
 		rabbitTemplate.convertAndSend("foo", "bar");
-		verify(mockChannel, times(2)).basicPublish(any(), any(), anyBoolean(),  any(), any());
+		verify(mockChannel, times(2)).basicPublish(any(), any(), anyBoolean(), any(), any());
 	}
 
 	@Test
@@ -1300,7 +1301,6 @@ public void onClose(Connection connection) {
 		verify(mockConnections.get(3)).close(30000);
 	}
 
-
 	@Test
 	public void testWithConnectionFactoryCachedConnectionAndChannels() throws Exception {
 		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class);
@@ -1644,6 +1644,8 @@ private void verifyChannelIs(Channel mockChannel, Channel channel) {
 	@Test
 	public void setAddressesEmpty() throws Exception {
 		ConnectionFactory mock = mock(com.rabbitmq.client.ConnectionFactory.class);
+		given(mock.newConnection(any(ExecutorService.class), anyString()))
+				.willReturn(mock(com.rabbitmq.client.Connection.class));
 		CachingConnectionFactory ccf = new CachingConnectionFactory(mock);
 		ccf.setExecutor(mock(ExecutorService.class));
 		ccf.setHost("abc");
@@ -1663,6 +1665,8 @@ public void setAddressesEmpty() throws Exception {
 	@Test
 	public void setAddressesOneHost() throws Exception {
 		ConnectionFactory mock = mock(com.rabbitmq.client.ConnectionFactory.class);
+		given(mock.newConnection(any(), anyList(), anyString()))
+				.willReturn(mock(com.rabbitmq.client.Connection.class));
 		CachingConnectionFactory ccf = new CachingConnectionFactory(mock);
 		ccf.setAddresses("mq1");
 		ccf.createConnection();
@@ -1674,8 +1678,9 @@ public void setAddressesOneHost() throws Exception {
 
 	@Test
 	public void setAddressesTwoHosts() throws Exception {
-		ConnectionFactory mock = mock(com.rabbitmq.client.ConnectionFactory.class);
+		ConnectionFactory mock = mock();
 		willReturn(true).given(mock).isAutomaticRecoveryEnabled();
+		willReturn(mock(com.rabbitmq.client.Connection.class)).given(mock).newConnection(any(), anyList(), anyString());
 		CachingConnectionFactory ccf = new CachingConnectionFactory(mock);
 		ccf.setAddresses("mq1,mq2");
 		ccf.createConnection();
@@ -1683,7 +1688,8 @@ public void setAddressesTwoHosts() throws Exception {
 		verify(mock).setAutomaticRecoveryEnabled(false);
 		verify(mock).newConnection(
 				isNull(),
-				argThat((ArgumentMatcher>) a -> a.size() == 2 && a.contains(new Address("mq1")) && a.contains(new Address("mq2"))),
+				argThat((ArgumentMatcher>) a -> a.size() == 2
+						&& a.contains(new Address("mq1")) && a.contains(new Address("mq2"))),
 				anyString());
 		verifyNoMoreInteractions(mock);
 	}
@@ -1692,7 +1698,9 @@ public void setAddressesTwoHosts() throws Exception {
 	public void setUri() throws Exception {
 		URI uri = new URI("amqp://localhost:1234/%2f");
 
-		ConnectionFactory mock = mock(com.rabbitmq.client.ConnectionFactory.class);
+		ConnectionFactory mock = mock();
+		given(mock.newConnection(any(ExecutorService.class), anyString()))
+				.willReturn(mock(com.rabbitmq.client.Connection.class));
 		CachingConnectionFactory ccf = new CachingConnectionFactory(mock);
 		ccf.setExecutor(mock(ExecutorService.class));
 
@@ -1854,12 +1862,12 @@ public void testFirstConnectionDoesntWait() throws IOException, TimeoutException
 	@SuppressWarnings("unchecked")
 	@Test
 	public void testShuffleRandom() throws IOException, TimeoutException {
-		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class);
-		com.rabbitmq.client.Connection mockConnection = mock(com.rabbitmq.client.Connection.class);
+		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock();
+		com.rabbitmq.client.Connection mockConnection = mock();
 		Channel mockChannel = mock(Channel.class);
 
-		given(mockConnectionFactory.newConnection((ExecutorService) isNull(), any(List.class), anyString()))
-			.willReturn(mockConnection);
+		given(mockConnectionFactory.newConnection(any(), anyList(), anyString()))
+				.willReturn(mockConnection);
 		given(mockConnection.createChannel()).willReturn(mockChannel);
 		given(mockChannel.isOpen()).willReturn(true);
 		given(mockConnection.isOpen()).willReturn(true);
@@ -1873,11 +1881,11 @@ public void testShuffleRandom() throws IOException, TimeoutException {
 		ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class);
 		verify(mockConnectionFactory, times(100)).newConnection(isNull(), captor.capture(), anyString());
 		List firstAddress = captor.getAllValues()
-			.stream()
-			.map(addresses -> addresses.get(0).getHost())
-			.distinct()
-			.sorted()
-			.collect(Collectors.toList());
+				.stream()
+				.map(addresses -> addresses.get(0).getHost())
+				.distinct()
+				.sorted()
+				.collect(Collectors.toList());
 		assertThat(firstAddress).containsExactly("host1", "host2", "host3");
 	}
 
@@ -1888,8 +1896,8 @@ public void testShuffleInOrder() throws IOException, TimeoutException {
 		com.rabbitmq.client.Connection mockConnection = mock(com.rabbitmq.client.Connection.class);
 		Channel mockChannel = mock(Channel.class);
 
-		given(mockConnectionFactory.newConnection((ExecutorService) isNull(), any(List.class), anyString()))
-			.willReturn(mockConnection);
+		given(mockConnectionFactory.newConnection(isNull(), anyList(), anyString()))
+				.willReturn(mockConnection);
 		given(mockConnection.createChannel()).willReturn(mockChannel);
 		given(mockChannel.isOpen()).willReturn(true);
 		given(mockConnection.isOpen()).willReturn(true);
@@ -1903,17 +1911,17 @@ public void testShuffleInOrder() throws IOException, TimeoutException {
 		ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class);
 		verify(mockConnectionFactory, times(3)).newConnection(isNull(), captor.capture(), anyString());
 		List connectAddresses = captor.getAllValues()
-			.stream()
-			.map(addresses -> addresses.get(0).getHost())
-			.collect(Collectors.toList());
+				.stream()
+				.map(addresses -> addresses.get(0).getHost())
+				.collect(Collectors.toList());
 		assertThat(connectAddresses).containsExactly("host1", "host2", "host3");
 	}
 
 	@Test
 	void testResolver() throws Exception {
-		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class);
-		com.rabbitmq.client.Connection mockConnection = mock(com.rabbitmq.client.Connection.class);
-		Channel mockChannel = mock(Channel.class);
+		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock();
+		com.rabbitmq.client.Connection mockConnection = mock();
+		Channel mockChannel = mock();
 
 		AddressResolver resolver = () -> Collections.singletonList(Address.parseAddress("foo:5672"));
 		given(mockConnectionFactory.newConnection(any(ExecutorService.class), eq(resolver), anyString()))
@@ -1934,7 +1942,7 @@ void testResolver() throws Exception {
 
 	@Test
 	void nullShutdownCause() {
-		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class);
+		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock();
 		AbstractConnectionFactory cf = createConnectionFactory(mockConnectionFactory);
 		AtomicBoolean connShutDown = new AtomicBoolean();
 		cf.addConnectionListener(new ConnectionListener() {
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/ConnnectionListenerTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/ConnectionListenerTests.java
similarity index 97%
rename from spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/ConnnectionListenerTests.java
rename to spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/ConnectionListenerTests.java
index 03638c6a15..606c71cf4c 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/ConnnectionListenerTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/ConnectionListenerTests.java
@@ -27,10 +27,11 @@
 
 /**
  * @author Gary Russell
+ * @author DongMin Park
  * @since 2.2.17
  *
  */
-public class ConnnectionListenerTests {
+public class ConnectionListenerTests {
 
 	@Test
 	void cantConnectCCF() {
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/PublisherCallbackChannelTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/PublisherCallbackChannelTests.java
index 6dcd35b9c6..6872f3f96e 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/PublisherCallbackChannelTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/PublisherCallbackChannelTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2022 the original author or authors.
+ * Copyright 2019-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.
@@ -183,7 +183,7 @@ void confirmAlwaysAfterReturn() throws InterruptedException {
 		assertThat(listener.calls).containsExactly("return", "confirm", "return", "confirm");
 	}
 
-	private static class TheListener implements Listener {
+	private static final class TheListener implements Listener {
 
 		private final UUID uuid = UUID.randomUUID();
 
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/BatchingRabbitTemplateTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/BatchingRabbitTemplateTests.java
index e5587a979f..b554fdd14c 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/BatchingRabbitTemplateTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/BatchingRabbitTemplateTests.java
@@ -676,7 +676,7 @@ private int getStreamLevel(Object stream) throws Exception {
 		return TestUtils.getPropertyValue(zipStream, "def.level", Integer.class);
 	}
 
-	private static class HeaderPostProcessor implements MessagePostProcessor {
+	private static final class HeaderPostProcessor implements MessagePostProcessor {
 		@Override
 		public Message postProcessMessage(Message message) throws AmqpException {
 			message.getMessageProperties().getHeaders().put("someHeader", "someValue");
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitAdminDeclarationTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitAdminDeclarationTests.java
index 3d82f4c14d..a6d04075b3 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitAdminDeclarationTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitAdminDeclarationTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-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.
@@ -17,7 +17,7 @@
 package org.springframework.amqp.rabbit.core;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.fail;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyMap;
@@ -32,12 +32,9 @@
 import static org.mockito.Mockito.verify;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.junit.jupiter.api.Test;
@@ -50,7 +47,6 @@
 import org.springframework.amqp.core.Exchange;
 import org.springframework.amqp.core.Queue;
 import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
-import org.springframework.amqp.rabbit.connection.CachingConnectionFactory.CacheMode;
 import org.springframework.amqp.rabbit.connection.Connection;
 import org.springframework.amqp.rabbit.connection.ConnectionFactory;
 import org.springframework.amqp.rabbit.connection.ConnectionListener;
@@ -79,8 +75,8 @@ public void testUnconditional() throws Exception {
 		given(cf.createConnection()).willReturn(conn);
 		given(conn.createChannel(false)).willReturn(channel);
 		given(channel.queueDeclare("foo", true, false, false, new HashMap<>()))
-			.willReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0));
-		final AtomicReference listener = new AtomicReference();
+				.willReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0));
+		AtomicReference listener = new AtomicReference<>();
 		willAnswer(invocation -> {
 			listener.set((ConnectionListener) invocation.getArguments()[0]);
 			return null;
@@ -100,51 +96,10 @@ public void testUnconditional() throws Exception {
 		listener.get().onCreate(conn);
 
 		verify(channel).queueDeclare("foo", true, false, false, new HashMap<>());
-		verify(channel).exchangeDeclare("bar", "direct", true, false, false, new HashMap());
+		verify(channel).exchangeDeclare("bar", "direct", true, false, false, new HashMap<>());
 		verify(channel).queueBind("foo", "bar", "foo", new HashMap<>());
 	}
 
-	@Test
-	public void testNoDeclareWithCachedConnections() throws Exception {
-		com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class);
-
-		final List mockChannels = new ArrayList();
-
-		AtomicInteger connectionNumber = new AtomicInteger();
-		willAnswer(invocation -> {
-			com.rabbitmq.client.Connection connection = mock(com.rabbitmq.client.Connection.class);
-			AtomicInteger channelNumber = new AtomicInteger();
-			willAnswer(invocation1 -> {
-				Channel channel = mock(Channel.class);
-				given(channel.isOpen()).willReturn(true);
-				int channelNum = channelNumber.incrementAndGet();
-				given(channel.toString()).willReturn("mockChannel" + channelNum);
-				mockChannels.add(channel);
-				return channel;
-			}).given(connection).createChannel();
-			int connectionNum = connectionNumber.incrementAndGet();
-			given(connection.toString()).willReturn("mockConnection" + connectionNum);
-			given(connection.isOpen()).willReturn(true);
-			return connection;
-		}).given(mockConnectionFactory).newConnection((ExecutorService) null);
-
-		CachingConnectionFactory ccf = new CachingConnectionFactory(mockConnectionFactory);
-		ccf.setCacheMode(CacheMode.CONNECTION);
-		ccf.afterPropertiesSet();
-
-		RabbitAdmin admin = new RabbitAdmin(ccf);
-		GenericApplicationContext context = new GenericApplicationContext();
-		Queue queue = new Queue("foo");
-		context.getBeanFactory().registerSingleton("foo", queue);
-		context.refresh();
-		admin.setApplicationContext(context);
-		admin.afterPropertiesSet();
-		ccf.createConnection().close();
-		ccf.destroy();
-
-		assertThat(mockChannels.size()).as("Admin should not have created a channel").isEqualTo(0);
-	}
-
 	@Test
 	public void testUnconditionalWithExplicitFactory() throws Exception {
 		ConnectionFactory cf = mock(ConnectionFactory.class);
@@ -153,8 +108,8 @@ public void testUnconditionalWithExplicitFactory() throws Exception {
 		given(cf.createConnection()).willReturn(conn);
 		given(conn.createChannel(false)).willReturn(channel);
 		given(channel.queueDeclare("foo", true, false, false, new HashMap<>()))
-			.willReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0));
-		final AtomicReference listener = new AtomicReference();
+				.willReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0));
+		AtomicReference listener = new AtomicReference<>();
 		willAnswer(invocation -> {
 			listener.set(invocation.getArgument(0));
 			return null;
@@ -177,7 +132,7 @@ public void testUnconditionalWithExplicitFactory() throws Exception {
 		listener.get().onCreate(conn);
 
 		verify(channel).queueDeclare("foo", true, false, false, new HashMap<>());
-		verify(channel).exchangeDeclare("bar", "direct", true, false, false, new HashMap());
+		verify(channel).exchangeDeclare("bar", "direct", true, false, false, new HashMap<>());
 		verify(channel).queueBind("foo", "bar", "foo", new HashMap<>());
 	}
 
@@ -189,8 +144,9 @@ public void testSkipBecauseDifferentFactory() throws Exception {
 		Channel channel = mock(Channel.class);
 		given(cf.createConnection()).willReturn(conn);
 		given(conn.createChannel(false)).willReturn(channel);
-		given(channel.queueDeclare("foo", true, false, false, null)).willReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0));
-		final AtomicReference listener = new AtomicReference();
+		given(channel.queueDeclare("foo", true, false, false, null))
+				.willReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0));
+		AtomicReference listener = new AtomicReference<>();
 		willAnswer(invocation -> {
 			listener.set(invocation.getArgument(0));
 			return null;
@@ -215,20 +171,21 @@ public void testSkipBecauseDifferentFactory() throws Exception {
 
 		verify(channel, never()).queueDeclare(eq("foo"), anyBoolean(), anyBoolean(), anyBoolean(), any(Map.class));
 		verify(channel, never())
-			.exchangeDeclare(eq("bar"), eq("direct"), anyBoolean(), anyBoolean(), anyBoolean(), any(Map.class));
+				.exchangeDeclare(eq("bar"), eq("direct"), anyBoolean(), anyBoolean(), anyBoolean(), any(Map.class));
 		verify(channel, never()).queueBind(eq("foo"), eq("bar"), eq("foo"), any(Map.class));
 	}
 
 	@SuppressWarnings("unchecked")
 	@Test
-	public void testSkipBecauseShouldntDeclare() throws Exception {
+	public void testSkipBecauseShouldNotDeclare() throws Exception {
 		ConnectionFactory cf = mock(ConnectionFactory.class);
 		Connection conn = mock(Connection.class);
 		Channel channel = mock(Channel.class);
 		given(cf.createConnection()).willReturn(conn);
 		given(conn.createChannel(false)).willReturn(channel);
-		given(channel.queueDeclare("foo", true, false, false, null)).willReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0));
-		final AtomicReference listener = new AtomicReference();
+		given(channel.queueDeclare("foo", true, false, false, null))
+				.willReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0));
+		AtomicReference listener = new AtomicReference<>();
 		willAnswer(invocation -> {
 			listener.set(invocation.getArgument(0));
 			return null;
@@ -252,7 +209,7 @@ public void testSkipBecauseShouldntDeclare() throws Exception {
 
 		verify(channel, never()).queueDeclare(eq("foo"), anyBoolean(), anyBoolean(), anyBoolean(), any(Map.class));
 		verify(channel, never())
-			.exchangeDeclare(eq("bar"), eq("direct"), anyBoolean(), anyBoolean(), anyBoolean(), any(Map.class));
+				.exchangeDeclare(eq("bar"), eq("direct"), anyBoolean(), anyBoolean(), anyBoolean(), any(Map.class));
 		verify(channel, never()).queueBind(eq("foo"), eq("bar"), eq("foo"), any(Map.class));
 	}
 
@@ -263,9 +220,8 @@ public void testJavaConfig() throws Exception {
 		verify(Config.channel1).queueDeclare("foo", true, false, false, new HashMap<>());
 		verify(Config.channel1, never()).queueDeclare("baz", true, false, false, new HashMap<>());
 		verify(Config.channel1).queueDeclare("qux", true, false, false, new HashMap<>());
-		verify(Config.channel1).exchangeDeclare("bar", "direct", true, false, true, new HashMap());
+		verify(Config.channel1).exchangeDeclare("bar", "direct", true, false, true, new HashMap<>());
 		verify(Config.channel1).queueBind("foo", "bar", "foo", new HashMap<>());
-
 		Config.listener2.onCreate(Config.conn2);
 		verify(Config.channel2, never())
 				.queueDeclare(eq("foo"), anyBoolean(), anyBoolean(), anyBoolean(), isNull());
@@ -273,9 +229,8 @@ public void testJavaConfig() throws Exception {
 		verify(Config.channel2).queueDeclare("qux", true, false, false, new HashMap<>());
 		verify(Config.channel2, never())
 				.exchangeDeclare(eq("bar"), eq("direct"), anyBoolean(), anyBoolean(),
-								anyBoolean(), anyMap());
+						anyBoolean(), anyMap());
 		verify(Config.channel2, never()).queueBind(eq("foo"), eq("bar"), eq("foo"), anyMap());
-
 		Config.listener3.onCreate(Config.conn3);
 		verify(Config.channel3, never())
 				.queueDeclare(eq("foo"), anyBoolean(), anyBoolean(), anyBoolean(), isNull());
@@ -286,7 +241,7 @@ public void testJavaConfig() throws Exception {
 		verify(Config.channel3, never()).queueDeclare("qux", true, false, false, new HashMap<>());
 		verify(Config.channel3, never())
 				.exchangeDeclare(eq("bar"), eq("direct"), anyBoolean(), anyBoolean(),
-								anyBoolean(), anyMap());
+						anyBoolean(), anyMap());
 		verify(Config.channel3, never()).queueBind(eq("foo"), eq("bar"), eq("foo"), anyMap());
 
 		context.close();
@@ -316,13 +271,9 @@ public void testAddRemove() {
 		assertThat(queue.getDeclaringAdmins()).hasSize(2);
 		queue.setAdminsThatShouldDeclare((Object[]) null);
 		assertThat(queue.getDeclaringAdmins()).hasSize(0);
-		try {
-			queue.setAdminsThatShouldDeclare(null, admin1);
-			fail("Expected Exception");
-		}
-		catch (IllegalArgumentException e) {
-			assertThat(e.getMessage()).contains("'admins' cannot contain null elements");
-		}
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> queue.setAdminsThatShouldDeclare(null, admin1))
+				.withMessageContaining("'admins' cannot contain null elements");
 	}
 
 	@Test
@@ -348,17 +299,17 @@ public void testNoOpWhenNothingToDeclare() throws Exception {
 	@Configuration
 	public static class Config {
 
-		private static Connection conn1 = mock(Connection.class);
+		private static final Connection conn1 = mock();
 
-		private static Connection conn2 = mock(Connection.class);
+		private static final Connection conn2 = mock();
 
-		private static Connection conn3 = mock(Connection.class);
+		private static final Connection conn3 = mock();
 
-		private static Channel channel1 = mock(Channel.class);
+		private static final Channel channel1 = mock();
 
-		private static Channel channel2 = mock(Channel.class);
+		private static final Channel channel2 = mock();
 
-		private static Channel channel3 = mock(Channel.class);
+		private static final Channel channel3 = mock();
 
 		private static ConnectionListener listener1;
 
@@ -371,9 +322,9 @@ public ConnectionFactory cf1() throws IOException {
 			ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
 			given(connectionFactory.createConnection()).willReturn(conn1);
 			given(conn1.createChannel(false)).willReturn(channel1);
-			willAnswer(inv -> {
-				return new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0);
-			}).given(channel1).queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any());
+			willAnswer(inv -> new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0))
+					.given(channel1)
+					.queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any());
 			willAnswer(invocation -> {
 				listener1 = invocation.getArgument(0);
 				return null;
@@ -386,9 +337,9 @@ public ConnectionFactory cf2() throws IOException {
 			ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
 			given(connectionFactory.createConnection()).willReturn(conn2);
 			given(conn2.createChannel(false)).willReturn(channel2);
-			willAnswer(inv -> {
-				return new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0);
-			}).given(channel2).queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any());
+			willAnswer(inv -> new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0))
+					.given(channel2)
+					.queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any());
 			willAnswer(invocation -> {
 				listener2 = invocation.getArgument(0);
 				return null;
@@ -401,9 +352,9 @@ public ConnectionFactory cf3() throws IOException {
 			ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
 			given(connectionFactory.createConnection()).willReturn(conn3);
 			given(conn3.createChannel(false)).willReturn(channel3);
-			willAnswer(inv -> {
-				return new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0);
-			}).given(channel3).queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any());
+			willAnswer(inv -> new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0))
+					.given(channel3)
+					.queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any());
 			willAnswer(invocation -> {
 				listener3 = invocation.getArgument(0);
 				return null;
@@ -413,14 +364,12 @@ public ConnectionFactory cf3() throws IOException {
 
 		@Bean
 		public RabbitAdmin admin1() throws IOException {
-			RabbitAdmin rabbitAdmin = new RabbitAdmin(cf1());
-			return rabbitAdmin;
+			return new RabbitAdmin(cf1());
 		}
 
 		@Bean
 		public RabbitAdmin admin2() throws IOException {
-			RabbitAdmin rabbitAdmin = new RabbitAdmin(cf2());
-			return rabbitAdmin;
+			return new RabbitAdmin(cf2());
 		}
 
 		@Bean
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateIntegrationTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateIntegrationTests.java
index 00dea012c1..06109d6606 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateIntegrationTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateIntegrationTests.java
@@ -1733,7 +1733,7 @@ private class PlannedException extends RuntimeException {
 	}
 
 	@SuppressWarnings("serial")
-	private class TestTransactionManager extends AbstractPlatformTransactionManager {
+	private final class TestTransactionManager extends AbstractPlatformTransactionManager {
 
 		@Override
 		protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegrationTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegration1Tests.java
similarity index 99%
rename from spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegrationTests.java
rename to spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegration1Tests.java
index 1429b7f5e6..fef647063b 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegrationTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegration1Tests.java
@@ -95,8 +95,8 @@
  * @since 1.1
  *
  */
-@RabbitAvailable(queues = RabbitTemplatePublisherCallbacksIntegrationTests.ROUTE)
-public class RabbitTemplatePublisherCallbacksIntegrationTests {
+@RabbitAvailable(queues = RabbitTemplatePublisherCallbacksIntegration1Tests.ROUTE)
+public class RabbitTemplatePublisherCallbacksIntegration1Tests {
 
 	public static final String ROUTE = "test.queue.RabbitTemplatePublisherCallbacksIntegrationTests";
 
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegrationTests2.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegration2Tests.java
similarity index 96%
rename from spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegrationTests2.java
rename to spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegration2Tests.java
index 450c1566a8..06837b9895 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegrationTests2.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegration2Tests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2022 the original author or authors.
+ * Copyright 2016-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.
@@ -41,9 +41,9 @@
  * @since 1.6
  *
  */
-@RabbitAvailable(queues = { RabbitTemplatePublisherCallbacksIntegrationTests2.ROUTE,
-		RabbitTemplatePublisherCallbacksIntegrationTests2.ROUTE2 })
-public class RabbitTemplatePublisherCallbacksIntegrationTests2 {
+@RabbitAvailable(queues = { RabbitTemplatePublisherCallbacksIntegration2Tests.ROUTE,
+		RabbitTemplatePublisherCallbacksIntegration2Tests.ROUTE2 })
+public class RabbitTemplatePublisherCallbacksIntegration2Tests {
 
 	public static final String ROUTE = "test.queue.RabbitTemplatePublisherCallbacksIntegrationTests2";
 
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegrationTests3.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegration3Tests.java
similarity index 95%
rename from spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegrationTests3.java
rename to spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegration3Tests.java
index 8e7fcef70e..d9f1dddd47 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegrationTests3.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplatePublisherCallbacksIntegration3Tests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2021 the original author or authors.
+ * Copyright 2018-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.
@@ -41,10 +41,10 @@
  * @since 2.1
  *
  */
-@RabbitAvailable(queues = { RabbitTemplatePublisherCallbacksIntegrationTests3.QUEUE1,
-		RabbitTemplatePublisherCallbacksIntegrationTests3.QUEUE2,
-		RabbitTemplatePublisherCallbacksIntegrationTests3.QUEUE3 })
-public class RabbitTemplatePublisherCallbacksIntegrationTests3 {
+@RabbitAvailable(queues = { RabbitTemplatePublisherCallbacksIntegration3Tests.QUEUE1,
+		RabbitTemplatePublisherCallbacksIntegration3Tests.QUEUE2,
+		RabbitTemplatePublisherCallbacksIntegration3Tests.QUEUE3 })
+public class RabbitTemplatePublisherCallbacksIntegration3Tests {
 
 	public static final String QUEUE1 = "synthetic.nack";
 
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateTests.java
index cc8683616e..5e6fb2a6d3 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateTests.java
@@ -742,7 +742,7 @@ protected void doRollback(DefaultTransactionStatus status) throws TransactionExc
 
 	}
 
-	private class DoNothingMPP implements MessagePostProcessor {
+	private static final class DoNothingMPP implements MessagePostProcessor {
 
 		@Override
 		public Message postProcessMessage(Message message) throws AmqpException {
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/AsyncReplyToTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/AsyncReplyToTests.java
index e5c43a5fd7..23e6bb17d5 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/AsyncReplyToTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/AsyncReplyToTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021-2022 the original author or authors.
+ * Copyright 2021-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.
@@ -44,6 +44,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
 
 import com.rabbitmq.client.Channel;
@@ -55,6 +56,7 @@
  */
 @SpringJUnitConfig
 @RabbitAvailable(queues = { "async1", "async2" })
+@DirtiesContext
 public class AsyncReplyToTests {
 
 	@Test
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/BrokerEventListenerTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/BrokerEventListenerTests.java
index 2541493c7a..d5473bc3cb 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/BrokerEventListenerTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/BrokerEventListenerTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2019 the original author or authors.
+ * Copyright 2018-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.
@@ -48,6 +48,7 @@
  */
 @SpringJUnitConfig
 @RabbitAvailable
+@DirtiesContext
 public class BrokerEventListenerTests {
 
 	@Autowired
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/DlqExpiryTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/DlqExpiryTests.java
index 729a66e825..4b5d8ca51b 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/DlqExpiryTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/DlqExpiryTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2019 the original author or authors.
+ * Copyright 2018-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.
@@ -38,6 +38,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
 
 /**
@@ -47,6 +48,7 @@
  */
 @RabbitAvailable
 @SpringJUnitConfig
+@DirtiesContext
 public class DlqExpiryTests {
 
 	@Autowired
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/ErrorHandlerTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/ErrorHandlerTests.java
index 2d9bcf0947..6ae1f35f18 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/ErrorHandlerTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/ErrorHandlerTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2019 the original author or authors.
+ * Copyright 2016-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.
@@ -127,7 +127,7 @@ private void doTest(Throwable cause) {
 				new MessageProperties())));
 	}
 
-	private static class Foo {
+	private static final class Foo {
 
 		@SuppressWarnings("unused")
 		public void foo(String foo) {
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/MessageListenerContainerMultipleQueueIntegrationTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/MessageListenerContainerMultipleQueueIntegrationTests.java
index 155eeaa885..d2fe778b84 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/MessageListenerContainerMultipleQueueIntegrationTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/MessageListenerContainerMultipleQueueIntegrationTests.java
@@ -40,11 +40,12 @@
  * @author Mark Fisher
  * @author Gunnar Hillert
  * @author Gary Russell
+ * @author Artem Bilan
  */
-@RabbitAvailable(queues = { MessageListenerContainerMultipleQueueIntegrationTests.TEST_QUEUE_1,
-		MessageListenerContainerMultipleQueueIntegrationTests.TEST_QUEUE_2 })
-@LogLevels(level = "INFO", classes = { RabbitTemplate.class,
-			SimpleMessageListenerContainer.class, BlockingQueueConsumer.class })
+@RabbitAvailable(queues = {MessageListenerContainerMultipleQueueIntegrationTests.TEST_QUEUE_1,
+		MessageListenerContainerMultipleQueueIntegrationTests.TEST_QUEUE_2})
+@LogLevels(level = "INFO", classes = {RabbitTemplate.class,
+		SimpleMessageListenerContainer.class, BlockingQueueConsumer.class})
 public class MessageListenerContainerMultipleQueueIntegrationTests {
 
 	public static final String TEST_QUEUE_1 = "test.queue.1.MessageListenerContainerMultipleQueueIntegrationTests";
@@ -77,7 +78,6 @@ public void testMultipleQueueNamesWithConcurrentConsumers() {
 		doTest(3, container -> container.setQueueNames(queue1.getName(), queue2.getName()));
 	}
 
-
 	private void doTest(int concurrentConsumers, ContainerConfigurer configurer) {
 		int messageCount = 10;
 		RabbitTemplate template = new RabbitTemplate();
@@ -119,17 +119,15 @@ private void doTest(int concurrentConsumers, ContainerConfigurer configurer) {
 			container.shutdown();
 			assertThat(container.getActiveConsumerCount()).isEqualTo(0);
 		}
-		assertThat(template.receiveAndConvert(queue1.getName())).isNull();
-		assertThat(template.receiveAndConvert(queue2.getName())).isNull();
-
 		connectionFactory.destroy();
 	}
 
 	@FunctionalInterface
 	private interface ContainerConfigurer {
+
 		void configure(SimpleMessageListenerContainer container);
-	}
 
+	}
 
 	@SuppressWarnings("unused")
 	private static class PojoListener {
@@ -150,6 +148,7 @@ public void handleMessage(int value) throws Exception {
 		public int getCount() {
 			return count.get();
 		}
+
 	}
 
 }
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/SimpleMessageListenerContainerTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/SimpleMessageListenerContainerTests.java
index f668ba0276..ed4580e603 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/SimpleMessageListenerContainerTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/SimpleMessageListenerContainerTests.java
@@ -882,7 +882,7 @@ private void waitForConsumersToStop(Set consumers) {
 	}
 
 	@SuppressWarnings("serial")
-	private static class TestTransactionManager extends AbstractPlatformTransactionManager {
+	private static final class TestTransactionManager extends AbstractPlatformTransactionManager {
 
 		@Override
 		protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/adapter/BatchMessagingMessageListenerAdapterTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/adapter/BatchMessagingMessageListenerAdapterTests.java
index d12f1d5a51..978551e288 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/adapter/BatchMessagingMessageListenerAdapterTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/adapter/BatchMessagingMessageListenerAdapterTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022-2023 the original author or authors.
+ * Copyright 2022-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.
@@ -44,6 +44,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -57,6 +58,7 @@
  */
 @SpringJUnitConfig
 @RabbitAvailable(queues = "test.batchQueue")
+@DirtiesContext
 public class BatchMessagingMessageListenerAdapterTests {
 
 	@Test
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/adapter/MessagingMessageListenerAdapterTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/adapter/MessagingMessageListenerAdapterTests.java
index 157e999a0f..f59ae1ce60 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/adapter/MessagingMessageListenerAdapterTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/listener/adapter/MessagingMessageListenerAdapterTests.java
@@ -488,7 +488,7 @@ public void withHeaders(Foo foo, @Headers Map headers) {
 
 	}
 
-	private static class Foo {
+	private static final class Foo {
 
 		private String foo;
 
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/support/micrometer/ObservationIntegrationTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/support/micrometer/ObservationIntegrationTests.java
index 41c7facb86..48e981c2f1 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/support/micrometer/ObservationIntegrationTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/support/micrometer/ObservationIntegrationTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 the original author or authors.
+ * Copyright 2022-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.
@@ -21,7 +21,6 @@
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
 
 import org.springframework.amqp.core.Message;
 import org.springframework.amqp.rabbit.annotation.EnableRabbit;
@@ -35,6 +34,7 @@
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import io.micrometer.common.KeyValue;
 import io.micrometer.common.KeyValues;
 import io.micrometer.core.tck.MeterRegistryAssert;
 import io.micrometer.observation.ObservationRegistry;
@@ -72,40 +72,67 @@ public SampleTestRunnerConsumer yourCode() {
 					.hasSize(4);
 			List producerSpans = finishedSpans.stream()
 					.filter(span -> span.getKind().equals(Kind.PRODUCER))
-					.collect(Collectors.toList());
+					.toList();
 			List consumerSpans = finishedSpans.stream()
 					.filter(span -> span.getKind().equals(Kind.CONSUMER))
-					.collect(Collectors.toList());
+					.toList();
 			SpanAssert.assertThat(producerSpans.get(0))
-					.hasTag("spring.rabbit.template.name", "template");
+					.hasTag("spring.rabbit.template.name", "template")
+					.hasTag("messaging.destination.name", "")
+					.hasTag("messaging.rabbitmq.destination.routing_key", "int.observation.testQ1");
 			SpanAssert.assertThat(producerSpans.get(0))
 					.hasRemoteServiceNameEqualTo("RabbitMQ");
 			SpanAssert.assertThat(producerSpans.get(1))
-					.hasTag("spring.rabbit.template.name", "template");
+					.hasTag("spring.rabbit.template.name", "template")
+					.hasTag("messaging.destination.name", "")
+					.hasTag("messaging.rabbitmq.destination.routing_key", "int.observation.testQ2");
 			SpanAssert.assertThat(consumerSpans.get(0))
-					.hasTagWithKey("spring.rabbit.listener.id");
+					.hasTagWithKey("spring.rabbit.listener.id")
+					.hasTag("messaging.destination.name", "int.observation.testQ1")
+					.hasTag("messaging.rabbitmq.message.delivery_tag", "1");
 			SpanAssert.assertThat(consumerSpans.get(0))
 					.hasRemoteServiceNameEqualTo("RabbitMQ");
 			assertThat(consumerSpans.get(0).getTags().get("spring.rabbit.listener.id")).isIn("obs1", "obs2");
 			SpanAssert.assertThat(consumerSpans.get(1))
 					.hasTagWithKey("spring.rabbit.listener.id");
 			assertThat(consumerSpans.get(1).getTags().get("spring.rabbit.listener.id")).isIn("obs1", "obs2");
+			SpanAssert.assertThat(consumerSpans.get(1))
+					.hasTagWithKey("spring.rabbit.listener.id")
+					.hasTag("messaging.destination.name", "int.observation.testQ2")
+					.hasTag("messaging.rabbitmq.message.delivery_tag", "1");
 			assertThat(consumerSpans.get(0).getTags().get("spring.rabbit.listener.id"))
 					.isNotEqualTo(consumerSpans.get(1).getTags().get("spring.rabbit.listener.id"));
 
 			MeterRegistryAssert.assertThat(getMeterRegistry())
 					.hasTimerWithNameAndTags("spring.rabbit.template",
-							KeyValues.of("spring.rabbit.template.name", "template"))
+							KeyValues.of(
+									KeyValue.of("spring.rabbit.template.name", "template"),
+									KeyValue.of("messaging.destination.name", ""),
+									KeyValue.of("messaging.rabbitmq.destination.routing_key", "int.observation.testQ1")
+							)
+					)
 					.hasTimerWithNameAndTags("spring.rabbit.template",
-							KeyValues.of("spring.rabbit.template.name", "template"))
+							KeyValues.of(
+									KeyValue.of("spring.rabbit.template.name", "template"),
+									KeyValue.of("messaging.destination.name", ""),
+									KeyValue.of("messaging.rabbitmq.destination.routing_key", "int.observation.testQ2")
+							)
+					)
 					.hasTimerWithNameAndTags("spring.rabbit.listener",
-							KeyValues.of("spring.rabbit.listener.id", "obs1"))
+							KeyValues.of(
+									KeyValue.of("spring.rabbit.listener.id", "obs1"),
+									KeyValue.of("messaging.destination.name", "int.observation.testQ1")
+							)
+					)
 					.hasTimerWithNameAndTags("spring.rabbit.listener",
-							KeyValues.of("spring.rabbit.listener.id", "obs2"));
+							KeyValues.of(
+									KeyValue.of("spring.rabbit.listener.id", "obs2"),
+									KeyValue.of("messaging.destination.name", "int.observation.testQ2")
+							)
+					);
 		};
 	}
 
-
 	@Configuration
 	@EnableRabbit
 	public static class Config {
@@ -159,5 +186,4 @@ void listen2(Message in) {
 
 	}
 
-
 }
diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/support/micrometer/ObservationTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/support/micrometer/ObservationTests.java
index e1787df31f..0b6cc28325 100644
--- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/support/micrometer/ObservationTests.java
+++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/support/micrometer/ObservationTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 the original author or authors.
+ * Copyright 2022-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.
@@ -44,6 +44,7 @@
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.lang.Nullable;
+import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
 
 import io.micrometer.common.KeyValues;
@@ -66,11 +67,15 @@
 
 /**
  * @author Gary Russell
+ * @author Ngoc Nhan
+ * @author Artem Bilan
+ *
  * @since 3.0
  *
  */
 @SpringJUnitConfig
 @RabbitAvailable(queues = { "observation.testQ1", "observation.testQ2" })
+@DirtiesContext
 public class ObservationTests {
 
 	@Test
@@ -90,17 +95,17 @@ void endToEnd(@Autowired Listener listener, @Autowired RabbitTemplate template,
 		SimpleSpan span = spans.poll();
 		assertThat(span.getTags()).containsEntry("spring.rabbit.template.name", "template");
 		assertThat(span.getName()).isEqualTo("/observation.testQ1 send");
-		await().until(() -> spans.peekFirst().getTags().size() == 3);
+		await().until(() -> spans.peekFirst().getTags().size() == 5);
 		span = spans.poll();
 		assertThat(span.getTags())
 				.containsAllEntriesOf(
 						Map.of("spring.rabbit.listener.id", "obs1", "foo", "some foo value", "bar", "some bar value"));
 		assertThat(span.getName()).isEqualTo("observation.testQ1 receive");
-		await().until(() -> spans.peekFirst().getTags().size() == 1);
+		await().until(() -> spans.peekFirst().getTags().size() == 3);
 		span = spans.poll();
 		assertThat(span.getTags()).containsEntry("spring.rabbit.template.name", "template");
 		assertThat(span.getName()).isEqualTo("/observation.testQ2 send");
-		await().until(() -> spans.peekFirst().getTags().size() == 3);
+		await().until(() -> spans.peekFirst().getTags().size() == 5);
 		span = spans.poll();
 		assertThat(span.getTags())
 				.containsAllEntriesOf(
@@ -110,7 +115,9 @@ void endToEnd(@Autowired Listener listener, @Autowired RabbitTemplate template,
 
 			@Override
 			public KeyValues getLowCardinalityKeyValues(RabbitMessageSenderContext context) {
-				return super.getLowCardinalityKeyValues(context).and("foo", "bar");
+				return super.getLowCardinalityKeyValues(context).and("foo", "bar")
+					.and("messaging.destination.name", context.getExchange())
+					.and("messaging.rabbitmq.destination.routing_key", context.getRoutingKey());
 			}
 
 		});
@@ -135,19 +142,23 @@ public KeyValues getLowCardinalityKeyValues(RabbitMessageReceiverContext context
 		span = spans.poll();
 		assertThat(span.getTags()).containsEntry("spring.rabbit.template.name", "template");
 		assertThat(span.getTags()).containsEntry("foo", "bar");
+		assertThat(span.getTags()).containsEntry("messaging.destination.name", "");
+		assertThat(span.getTags()).containsEntry("messaging.rabbitmq.destination.routing_key", "observation.testQ1");
 		assertThat(span.getName()).isEqualTo("/observation.testQ1 send");
-		await().until(() -> spans.peekFirst().getTags().size() == 4);
+		await().until(() -> spans.peekFirst().getTags().size() == 6);
 		span = spans.poll();
 		assertThat(span.getTags())
 				.containsAllEntriesOf(Map.of("spring.rabbit.listener.id", "obs1", "foo", "some foo value", "bar",
 						"some bar value", "baz", "qux"));
 		assertThat(span.getName()).isEqualTo("observation.testQ1 receive");
-		await().until(() -> spans.peekFirst().getTags().size() == 2);
+		await().until(() -> spans.peekFirst().getTags().size() == 4);
 		span = spans.poll();
 		assertThat(span.getTags()).containsEntry("spring.rabbit.template.name", "template");
 		assertThat(span.getTags()).containsEntry("foo", "bar");
+		assertThat(span.getTags()).containsEntry("messaging.destination.name", "");
+		assertThat(span.getTags()).containsEntry("messaging.rabbitmq.destination.routing_key", "observation.testQ2");
 		assertThat(span.getName()).isEqualTo("/observation.testQ2 send");
-		await().until(() -> spans.peekFirst().getTags().size() == 3);
+		await().until(() -> spans.peekFirst().getTags().size() == 5);
 		span = spans.poll();
 		assertThat(span.getTags())
 				.containsAllEntriesOf(
diff --git a/spring-rabbit/src/test/kotlin/org/springframework/amqp/rabbit/annotation/EnableRabbitKotlinTests.kt b/spring-rabbit/src/test/kotlin/org/springframework/amqp/rabbit/annotation/EnableRabbitKotlinTests.kt
index 87fe6dba34..8e9f452f9f 100644
--- a/spring-rabbit/src/test/kotlin/org/springframework/amqp/rabbit/annotation/EnableRabbitKotlinTests.kt
+++ b/spring-rabbit/src/test/kotlin/org/springframework/amqp/rabbit/annotation/EnableRabbitKotlinTests.kt
@@ -19,9 +19,11 @@ package org.springframework.amqp.rabbit.annotation
 import assertk.assertThat
 import assertk.assertions.containsOnly
 import assertk.assertions.isEqualTo
+import assertk.assertions.isInstanceOf
 import assertk.assertions.isTrue
 import org.junit.jupiter.api.Test
 import org.springframework.amqp.core.AcknowledgeMode
+import org.springframework.amqp.core.Message
 import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory
 import org.springframework.amqp.rabbit.connection.CachingConnectionFactory
 import org.springframework.amqp.rabbit.core.RabbitTemplate
@@ -77,7 +79,8 @@ class EnableRabbitKotlinTests {
 		template.convertAndSend("kotlinBatchQueue", "test1")
 		template.convertAndSend("kotlinBatchQueue", "test2")
 		assertThat(this.config.batchReceived.await(10, TimeUnit.SECONDS)).isTrue()
-		assertThat(this.config.batch).containsOnly("test1", "test2")
+		assertThat(this.config.batch[0]).isInstanceOf(Message::class.java)
+		assertThat(this.config.batch.map { m -> String(m.body) }).containsOnly("test1", "test2")
 	}
 
 	@Test
@@ -100,13 +103,13 @@ class EnableRabbitKotlinTests {
 
 		val batchReceived = CountDownLatch(1)
 
-		lateinit var batch: List
+		lateinit var batch: List
 
 		@RabbitListener(id = "batch", queues = ["kotlinBatchQueue"],
 				containerFactory = "batchRabbitListenerContainerFactory")
-		suspend fun receiveBatch(messages: List) {
-			batchReceived.countDown()
+		suspend fun receiveBatch(messages: List) {
 			batch = messages
+			batchReceived.countDown()
 		}
 
 		@Bean
diff --git a/src/reference/antora/antora-playbook.yml b/src/reference/antora/antora-playbook.yml
index 333dfc2fb5..8397e13ecc 100644
--- a/src/reference/antora/antora-playbook.yml
+++ b/src/reference/antora/antora-playbook.yml
@@ -32,4 +32,4 @@ runtime:
     format: pretty
 ui:
   bundle:
-    url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.15/ui-bundle.zip
+    url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.17/ui-bundle.zip
diff --git a/src/reference/antora/antora.yml b/src/reference/antora/antora.yml
index fc3041c337..a3fb163dea 100644
--- a/src/reference/antora/antora.yml
+++ b/src/reference/antora/antora.yml
@@ -14,4 +14,17 @@ ext:
 asciidoc:
   attributes:
     attribute-missing: 'warn'
-    chomp: 'all'
\ No newline at end of file
+    chomp: 'all'
+    spring-docs: 'https://docs.spring.io'
+    spring-framework-docs: '{spring-docs}/spring-framework/reference'
+    spring-integration-docs: '{spring-docs}/spring-integration/reference'
+    spring-amqp-java-docs: '{spring-docs}/spring-amqp/docs/current/api/org/springframework/amqp'
+    spring-framework-java-docs: '{spring-docs}/spring/docs/current/javadoc-api/org/springframework'
+    spring-retry-java-docs: '{spring-docs}/spring-retry/docs/api/current/'
+    # External projects URLs and related attributes
+    micrometer-docs: 'https://docs.micrometer.io'
+    micrometer-tracing-docs: '{micrometer-docs}/tracing/reference/'
+    micrometer-micrometer-docs: '{micrometer-docs}/micrometer/reference/'
+    rabbitmq-stream-docs: 'https://rabbitmq.github.io/rabbitmq-stream-java-client/stable/htmlsingle'
+    rabbitmq-github: 'https://github.com/rabbitmq'
+    rabbitmq-server-github: '{rabbitmq-github}/rabbitmq-server/tree/main/deps'
\ No newline at end of file
diff --git a/src/reference/antora/modules/ROOT/nav.adoc b/src/reference/antora/modules/ROOT/nav.adoc
index a947118935..b4327b384a 100644
--- a/src/reference/antora/modules/ROOT/nav.adoc
+++ b/src/reference/antora/modules/ROOT/nav.adoc
@@ -71,14 +71,11 @@
 *** xref:appendix/previous-whats-new/changes-in-3-1-since-3-0.adoc[]
 *** xref:appendix/previous-whats-new/changes-in-3-0-since-2-4.adoc[]
 *** xref:appendix/previous-whats-new/changes-in-2-4-since-2-3.adoc[]
-*** xref:appendix/previous-whats-new/message-converter-changes.adoc[]
-*** xref:appendix/previous-whats-new/stream-support-changes.adoc[]
 *** xref:appendix/previous-whats-new/changes-in-2-3-since-2-2.adoc[]
 *** xref:appendix/previous-whats-new/changes-in-2-2-since-2-1.adoc[]
 *** xref:appendix/previous-whats-new/changes-in-2-1-since-2-0.adoc[]
 *** xref:appendix/previous-whats-new/changes-in-2-0-since-1-7.adoc[]
 *** xref:appendix/previous-whats-new/changes-in-1-7-since-1-6.adoc[]
-*** xref:appendix/previous-whats-new/earlier-releases.adoc[]
 *** xref:appendix/previous-whats-new/changes-in-1-6-since-1-5.adoc[]
 *** xref:appendix/previous-whats-new/changes-in-1-5-since-1-4.adoc[]
 *** xref:appendix/previous-whats-new/changes-in-1-4-since-1-3.adoc[]
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/abstractions.adoc b/src/reference/antora/modules/ROOT/pages/amqp/abstractions.adoc
index b86178a244..9d7bad429f 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/abstractions.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/abstractions.adoc
@@ -93,7 +93,7 @@ Starting with version 3.2, the `ConsistentHashExchange` type has been introduced
 It provided options like `x-consistent-hash` for an exchange type.
 Allows to configure `hash-header` or `hash-property` exchange definition argument.
 The respective RabbitMQ `rabbitmq_consistent_hash_exchange` plugin has to be enabled on the broker.
-More information about the purpose, logic and behavior of the Consistent Hash Exchange are in the official RabbitMQ https://github.com/rabbitmq/rabbitmq-server/tree/main/deps/rabbitmq_consistent_hash_exchange[documentation].
+More information about the purpose, logic and behavior of the Consistent Hash Exchange are in the official RabbitMQ {rabbitmq-server-github}/rabbitmq_consistent_hash_exchange[documentation].
 
 NOTE: The AMQP specification also requires that any broker provide a "`default`" direct exchange that has no name.
 All queues that are declared are bound to that default `Exchange` with their names as routing keys.
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/broker-configuration.adoc b/src/reference/antora/modules/ROOT/pages/amqp/broker-configuration.adoc
index c983716a37..1dbf6ac314 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/broker-configuration.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/broker-configuration.adoc
@@ -359,7 +359,7 @@ public Exchange exchange() {
 }
 ----
 
-See the Javadoc for https://docs.spring.io/spring-amqp/docs/current/api/org/springframework/amqp/core/QueueBuilder.html[`org.springframework.amqp.core.QueueBuilder`] and https://docs.spring.io/spring-amqp/docs/current/api/org/springframework/amqp/core/ExchangeBuilder.html[`org.springframework.amqp.core.ExchangeBuilder`] for more information.
+See the Javadoc for {spring-amqp-java-docs}/core/QueueBuilder.html[`org.springframework.amqp.core.QueueBuilder`] and {spring-amqp-java-docs}/core/ExchangeBuilder.html[`org.springframework.amqp.core.ExchangeBuilder`] for more information.
 
 Starting with version 2.0, the `ExchangeBuilder` now creates durable exchanges by default, to be consistent with the simple constructors on the individual `AbstractExchange` classes.
 To make a non-durable exchange with the builder, use `.durable(false)` before invoking `.build()`.
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/connections.adoc b/src/reference/antora/modules/ROOT/pages/amqp/connections.adoc
index 42a4b0a199..c1b278a820 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/connections.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/connections.adoc
@@ -429,7 +429,7 @@ Starting with version 3.0, the underlying connection factory will attempt to con
 To revert to the previous behavior of attempting to connect from first to last, set the `addressShuffleMode` property to `AddressShuffleMode.NONE`.
 
 Starting with version 2.3, the `INORDER` shuffle mode was added, which means the first address is moved to the end after a connection is created.
-You may wish to use this mode with the https://github.com/rabbitmq/rabbitmq-sharding[RabbitMQ Sharding Plugin] with `CacheMode.CONNECTION` and suitable concurrency if you wish to consume from all shards on all nodes.
+You may wish to use this mode with the {rabbitmq-server-github}/rabbitmq_sharding[RabbitMQ Sharding Plugin] with `CacheMode.CONNECTION` and suitable concurrency if you wish to consume from all shards on all nodes.
 
 [source, java]
 ----
@@ -483,7 +483,7 @@ public class MyService {
 ----
 
 It is important to unbind the resource after use.
-For more information, see the https://docs.spring.io/spring-amqp/docs/current/api/org/springframework/amqp/rabbit/connection/AbstractRoutingConnectionFactory.html[JavaDoc] for `AbstractRoutingConnectionFactory`.
+For more information, see the {spring-amqp-java-docs}/rabbit/connection/AbstractRoutingConnectionFactory.html[JavaDoc] for `AbstractRoutingConnectionFactory`.
 
 Starting with version 1.4, `RabbitTemplate` supports the SpEL `sendConnectionFactorySelectorExpression` and `receiveConnectionFactorySelectorExpression` properties, which are evaluated on each AMQP protocol interaction operation (`send`, `sendAndReceive`, `receive`, or `receiveAndReply`), resolving to a `lookupKey` value for the provided `AbstractRoutingConnectionFactory`.
 You can use bean references, such as `@vHostResolver.getVHost(#root)` in the expression.
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/containerAttributes.adoc b/src/reference/antora/modules/ROOT/pages/amqp/containerAttributes.adoc
index 9aec7d7c9b..f3172bf9fe 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/containerAttributes.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/containerAttributes.adoc
@@ -250,7 +250,7 @@ a|
 |[[consumeDelay]]<> +
 (N/A)
 
-|When using the https://github.com/rabbitmq/rabbitmq-sharding[RabbitMQ Sharding Plugin] with `concurrentConsumers > 1`, there is a race condition that can prevent even distribution of the consumers across the shards.
+|When using the {rabbitmq-server-github}/rabbitmq_sharding[RabbitMQ Sharding Plugin] with `concurrentConsumers > 1`, there is a race condition that can prevent even distribution of the consumers across the shards.
 Use this property to add a small delay between consumer starts to avoid this race condition.
 You should experiment with values to determine the suitable delay for your environment.
 
@@ -435,7 +435,7 @@ a|
 (mismatched-queues-fatal)
 
 a|When the container starts, if this property is `true` (default: `false`), the container checks that all queues declared in the context are compatible with queues already on the broker.
-If mismatched properties (such as `auto-delete`) or arguments (skuch as `x-message-ttl`) exist, the container (and application context) fails to start with a fatal exception.
+If mismatched properties (such as `auto-delete`) or arguments (such as `x-message-ttl`) exist, the container (and application context) fails to start with a fatal exception.
 
 If the problem is detected during recovery (for example, after a lost connection), the container is stopped.
 
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/listener-queues.adoc b/src/reference/antora/modules/ROOT/pages/amqp/listener-queues.adoc
index 03fe292c7a..ed6101ec5c 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/listener-queues.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/listener-queues.adoc
@@ -8,7 +8,7 @@ Container can be initially configured to listen on zero queues.
 Queues can be added and removed at runtime.
 The `SimpleMessageListenerContainer` recycles (cancels and re-creates) all consumers when any pre-fetched messages have been processed.
 The `DirectMessageListenerContainer` creates/cancels individual consumer(s) for each queue without affecting consumers on other queues.
-See the https://docs.spring.io/spring-amqp/docs/current/api/org/springframework/amqp/rabbit/listener/AbstractMessageListenerContainer.html[Javadoc] for the `addQueues`, `addQueueNames`, `removeQueues` and `removeQueueNames` methods.
+See the {spring-amqp-java-docs}/rabbit/listener/AbstractMessageListenerContainer.html[Javadoc] for the `addQueues`, `addQueueNames`, `removeQueues` and `removeQueueNames` methods.
 
 If not all queues are available, the container tries to passively declare (and consume from) the missing queues every 60 seconds.
 
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/management-rest-api.adoc b/src/reference/antora/modules/ROOT/pages/amqp/management-rest-api.adoc
index 445b46c8a1..628c0ff7b1 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/management-rest-api.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/management-rest-api.adoc
@@ -3,9 +3,9 @@
 :page-section-summary-toc: 1
 
 When the management plugin is enabled, the RabbitMQ server exposes a REST API to monitor and configure the broker.
-A https://github.com/rabbitmq/hop[Java Binding for the API] is now provided.
+A {rabbitmq-github}/hop[Java Binding for the API] is now provided.
 The `com.rabbitmq.http.client.Client` is a standard, immediate, and, therefore, blocking API.
-It is based on the https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#spring-web[Spring Web] module and its `RestTemplate` implementation.
+It is based on the {spring-framework-docs}/web.html[Spring Web] module and its `RestTemplate` implementation.
 On the other hand, the `com.rabbitmq.http.client.ReactorNettyClient` is a reactive, non-blocking implementation based on the https://projectreactor.io/docs/netty/release/reference/docs/index.html[Reactor Netty] project.
 
 The hop dependency (`com.rabbitmq:http-client`) is now also `optional`.
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/message-converters.adoc b/src/reference/antora/modules/ROOT/pages/amqp/message-converters.adoc
index b828342c4b..c028af61bd 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/message-converters.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/message-converters.adoc
@@ -356,7 +356,7 @@ It has been replaced by `AbstractJackson2MessageConverter`.
 
 Yet another option is the `MarshallingMessageConverter`.
 It delegates to the Spring OXM library's implementations of the `Marshaller` and `Unmarshaller` strategy interfaces.
-You can read more about that library https://docs.spring.io/spring/docs/current/spring-framework-reference/html/oxm.html[here].
+You can read more about that library {spring-framework-docs}/data-access/oxm.html[here].
 In terms of configuration, it is most common to provide only the constructor argument, since most implementations of `Marshaller` also implement `Unmarshaller`.
 The following example shows how to configure a `MarshallingMessageConverter`:
 
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/enable.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/enable.adoc
index 71d39dc08b..422de556ef 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/enable.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/enable.adoc
@@ -37,7 +37,7 @@ In this case, and ignoring the RabbitMQ infrastructure setup, the `processOrder`
 
 You can customize the listener container factory to use for each annotation, or you can configure an explicit default by implementing the `RabbitListenerConfigurer` interface.
 The default is required only if at least one endpoint is registered without a specific container factory.
-See the https://docs.spring.io/spring-amqp/docs/current/api/org/springframework/amqp/rabbit/annotation/RabbitListenerConfigurer.html[Javadoc] for full details and examples.
+See the {spring-amqp-java-docs}/rabbit/annotation/RabbitListenerConfigurer.html[Javadoc] for full details and examples.
 
 The container factories provide methods for adding `MessagePostProcessor` instances that are applied after receiving messages (before invoking the listener) and before sending replies.
 
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/error-handling.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/error-handling.adoc
index 9c26c69de0..fe273d6adf 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/error-handling.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/error-handling.adoc
@@ -36,7 +36,7 @@ If you use JSON, consider using an `errorHandler` to return some other Jackson-f
 
 IMPORTANT: In version 2.1, this interface moved from package `o.s.amqp.rabbit.listener` to `o.s.amqp.rabbit.listener.api`.
 
-Starting with version 2.1.7, the `Channel` is available in a messaging message header; this allows you to ack or nack the failed messasge when using `AcknowledgeMode.MANUAL`:
+Starting with version 2.1.7, the `Channel` is available in a messaging message header; this allows you to ack or nack the failed message when using `AcknowledgeMode.MANUAL`:
 
 [source, java]
 ----
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/registration.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/registration.adoc
index d1458af838..46db7e95b5 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/registration.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/async-annotation-driven/registration.adoc
@@ -14,6 +14,7 @@ public class AppConfig implements RabbitListenerConfigurer {
     @Override
     public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
         SimpleRabbitListenerEndpoint endpoint = new SimpleRabbitListenerEndpoint();
+		endpoint.setId("someRabbitListenerEndpoint");
         endpoint.setQueueNames("anotherQueue");
         endpoint.setMessageListener(message -> {
             // processing
@@ -25,5 +26,7 @@ public class AppConfig implements RabbitListenerConfigurer {
 
 In the preceding example, we used `SimpleRabbitListenerEndpoint`, which provides the actual `MessageListener` to invoke, but you could just as well build your own endpoint variant to describe a custom invocation mechanism.
 
+NOTE: the `id` property is required for `SimpleRabbitListenerEndpoint` definition.
+
 It should be noted that you could just as well skip the use of `@RabbitListener` altogether and register your endpoints programmatically through `RabbitListenerConfigurer`.
 
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer-observation.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer-observation.adoc
index 7d30d09065..bbd1c340ee 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer-observation.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer-observation.adoc
@@ -7,7 +7,7 @@ Using Micrometer for observation is now supported, since version 3.0, for the `R
 Set `observationEnabled` on each component to enable observation; this will disable xref:amqp/receiving-messages/micrometer.adoc[Micrometer Timers] because the timers will now be managed with each observation.
 When using annotated listeners, set `observationEnabled` on the container factory.
 
-Refer to https://docs.micrometer.io/tracing/reference/[Micrometer Tracing] for more information.
+Refer to {micrometer-tracing-docs}[Micrometer Tracing] for more information.
 
 To add tags to timers/traces, configure a custom `RabbitTemplateObservationConvention` or `RabbitListenerObservationConvention` to the template or listener container, respectively.
 
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer.adoc b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer.adoc
index efae24ebfb..bc581083ad 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/receiving-messages/micrometer.adoc
@@ -2,7 +2,7 @@
 = Micrometer Integration
 :page-section-summary-toc: 1
 
-NOTE: This section documents the integration with https://docs.micrometer.io/micrometer/reference/[Micrometer].
+NOTE: This section documents the integration with {micrometer-micrometer-docs}[Micrometer].
 For integration with Micrometer Observation, see xref:amqp/receiving-messages/micrometer-observation.adoc[Micrometer Observation].
 
 Starting with version 2.2, the listener containers will automatically create and update Micrometer `Timer` s for the listener, if `Micrometer` is detected on the class path, and a single `MeterRegistry` is present in the application context (or exactly one is annotated `@Primary`, such as when using Spring Boot).
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/request-reply.adoc b/src/reference/antora/modules/ROOT/pages/amqp/request-reply.adoc
index 57d515d987..3689437cb3 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/request-reply.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/request-reply.adoc
@@ -6,11 +6,11 @@ Those methods are quite useful for request-reply scenarios, since they handle th
 
 Similar request-reply methods are also available where the `MessageConverter` is applied to both the request and reply.
 Those methods are named `convertSendAndReceive`.
-See the https://docs.spring.io/spring-amqp/docs/current/api/org/springframework/amqp/core/AmqpTemplate.html[Javadoc of `AmqpTemplate`] for more detail.
+See the {spring-amqp-java-docs}/core/AmqpTemplate.html[Javadoc of `AmqpTemplate`] for more detail.
 
 Starting with version 1.5.0, each of the `sendAndReceive` method variants has an overloaded version that takes `CorrelationData`.
 Together with a properly configured connection factory, this enables the receipt of publisher confirms for the send side of the operation.
-See xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns] and the https://docs.spring.io/spring-amqp/docs/current/api/org/springframework/amqp/rabbit/core/RabbitOperations.html[Javadoc for `RabbitOperations`] for more information.
+See xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns] and the {spring-amqp-java-docs}/rabbit/core/RabbitOperations.html[Javadoc for `RabbitOperations`] for more information.
 
 Starting with version 2.0, there are variants of these methods (`convertSendAndReceiveAsType`) that take an additional `ParameterizedTypeReference` argument to convert complex returned types.
 The template must be configured with a `SmartMessageConverter`.
@@ -290,8 +290,6 @@ Version 2.0 introduced variants of these methods (`convertSendAndReceiveAsType`)
 You must configure the underlying `RabbitTemplate` with a `SmartMessageConverter`.
 See xref:amqp/message-converters.adoc#json-complex[Converting From a `Message` With `RabbitTemplate`] for more information.
 
-IMPORTANT: Starting with version 3.0, the `AsyncRabbitTemplate` methods now return `CompletableFuture` s instead of `ListenableFuture` s.
-
 [[remoting]]
 == Spring Remoting with AMQP
 
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/resilience-recovering-from-errors-and-broker-failures.adoc b/src/reference/antora/modules/ROOT/pages/amqp/resilience-recovering-from-errors-and-broker-failures.adoc
index 18f5107668..fb35a9723f 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/resilience-recovering-from-errors-and-broker-failures.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/resilience-recovering-from-errors-and-broker-failures.adoc
@@ -90,7 +90,7 @@ public StatefulRetryOperationsInterceptor interceptor() {
 
 Only a subset of retry capabilities can be configured this way.
 More advanced features would need the configuration of a `RetryTemplate` as a Spring bean.
-See the https://docs.spring.io/spring-retry/docs/api/current/[Spring Retry Javadoc] for complete information about available policies and their configuration.
+See the {spring-retry-java-docs}[Spring Retry Javadoc] for complete information about available policies and their configuration.
 
 [[batch-retry]]
 == Retry with Batch Listeners
@@ -210,3 +210,40 @@ When `true`, it travers exception causes until it finds a match or there is no c
 
 To use this classifier for retry, you can use a `SimpleRetryPolicy` created with the constructor that takes the max attempts, the `Map` of `Exception` instances, and the boolean (`traverseCauses`) and inject this policy into the `RetryTemplate`.
 
+[[retry-over-broker]]
+== Retry Over Broker
+
+The message dead-lettered from the queue can be republished back to this queue after re-routing from a DLX.
+This retry behaviour is controlled on the broker side via an `x-death` header.
+More information about this approach in the official https://www.rabbitmq.com/docs/dlx[RabbitMQ documentation].
+
+The other approach is to re-publish failed message back to the original exchange manually from the application.
+Starting with version `4.0`, the RabbitMQ broker does not consider `x-death` header sent from the client.
+Essentially, any `x-*` headers are ignored from the client.
+
+To mitigate this new behavior of the RabbitMQ broker, Spring AMQP has introduced a `retry_count` header starting with version 3.2.
+When this header is absent and a server side DLX is in action, the `x-death.count` property is mapped to this header.
+When the failed message is re-published manually for retries, the `retry_count` header value has to be incremented manually.
+See `MessageProperties.incrementRetryCount()` JavaDocs for more information.
+
+The following example summarise an algorithm for manual retry over the broker:
+
+[source,java]
+----
+@RabbitListener(queueNames = "some_queue")
+public void rePublish(Message message) {
+    try {
+    // Process message
+    }
+    catch (Exception ex) {
+        Long retryCount = message.getMessageProperties().getRetryCount();
+        if (retryCount < 3) {
+            message.getMessageProperties().incrementRetryCount();
+            this.rabbitTemplate.send("", "some_queue", message);
+        }
+        else {
+            throw new ImmediateAcknowledgeAmqpException("Failed after 4 attempts");
+		}
+    }
+}
+----
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/sending-messages.adoc b/src/reference/antora/modules/ROOT/pages/amqp/sending-messages.adoc
index 961b8daa13..6ff5f1257d 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/sending-messages.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/sending-messages.adoc
@@ -13,7 +13,7 @@ void send(String exchange, String routingKey, Message message) throws AmqpExcept
 ----
 
 We can begin our discussion with the last method in the preceding listing, since it is actually the most explicit.
-It lets an AMQP exchange name  (along with a routing key)be provided at runtime.
+It lets an AMQP exchange name  (along with a routing key) be provided at runtime.
 The last parameter is the callback that is responsible for actual creating the message instance.
 An example of using this method to send a message might look like this:
 The following example shows how to use the `send` method to send a message:
@@ -100,7 +100,7 @@ Message message = MessageBuilder.withBody("foo".getBytes())
     .build();
 ----
 
-Each of the properties defined on the https://docs.spring.io/spring-amqp/docs/current/api/org/springframework/amqp/core/MessageProperties.html[`MessageProperties`] can be set.
+Each of the properties defined on the {spring-amqp-java-docs}/core/MessageProperties.html[`MessageProperties`] can be set.
 Other methods include `setHeader(String key, String value)`, `removeHeader(String key)`, `removeHeaders()`, and `copyProperties(MessageProperties properties)`.
 Each property setting method has a `set*IfAbsent()` variant.
 In the cases where a default initial value exists, the method is named `set*IfAbsentOrDefault()`.
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/template.adoc b/src/reference/antora/modules/ROOT/pages/amqp/template.adoc
index 55b17e8cb3..0dea17c5d3 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/template.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/template.adoc
@@ -195,7 +195,7 @@ In general, this means that only one confirm is outstanding on a channel at a ti
 NOTE: Starting with version 2.2, the callbacks are invoked on one of the connection factory's `executor` threads.
 This is to avoid a potential deadlock if you perform Rabbit operations from within the callback.
 With previous versions, the callbacks were invoked directly on the `amqp-client` connection I/O thread; this would deadlock if you perform some RPC operation (such as opening a new channel) since the I/O thread blocks waiting for the result, but the result needs to be processed by the I/O thread itself.
-With those versions, it was necessary to hand off work (such as sending a messasge) to another thread within the callback.
+With those versions, it was necessary to hand off work (such as sending a message) to another thread within the callback.
 This is no longer necessary since the framework now hands off the callback invocation to the executor.
 
 IMPORTANT: The guarantee of receiving a returned message before the ack is still maintained as long as the return callback executes in 60 seconds or less.
diff --git a/src/reference/antora/modules/ROOT/pages/amqp/transactions.adoc b/src/reference/antora/modules/ROOT/pages/amqp/transactions.adoc
index c007b02199..59bb842f7b 100644
--- a/src/reference/antora/modules/ROOT/pages/amqp/transactions.adoc
+++ b/src/reference/antora/modules/ROOT/pages/amqp/transactions.adoc
@@ -69,7 +69,7 @@ If the `channelTransacted` flag was set to `false` (the default) in the precedin
 Prior to version 1.6.6, adding a rollback rule to a container's `transactionAttribute` when using an external transaction manager (such as JDBC) had no effect.
 Exceptions always rolled back the transaction.
 
-Also, when using a https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/transaction.html#transaction-declarative[transaction advice] in the container's advice chain, conditional rollback was not very useful, because all listener exceptions are wrapped in a `ListenerExecutionFailedException`.
+Also, when using a {spring-framework-docs}/data-access/transaction/declarative.html[transaction advice] in the container's advice chain, conditional rollback was not very useful, because all listener exceptions are wrapped in a `ListenerExecutionFailedException`.
 
 The first problem has been corrected, and the rules are now applied properly.
 Further, the `ListenerFailedRuleBasedTransactionAttribute` is now provided.
@@ -116,13 +116,13 @@ See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration]
 [[using-rabbittransactionmanager]]
 == Using `RabbitTransactionManager`
 
-The https://docs.spring.io/spring-amqp/docs/current/api/org/springframework/amqp/rabbit/transaction/RabbitTransactionManager.html[RabbitTransactionManager] is an alternative to executing Rabbit operations within, and synchronized with, external transactions.
-This transaction manager is an implementation of the https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/PlatformTransactionManager.html[`PlatformTransactionManager`] interface and should be used with a single Rabbit `ConnectionFactory`.
+The {spring-amqp-java-docs}/rabbit/transaction/RabbitTransactionManager.html[RabbitTransactionManager] is an alternative to executing Rabbit operations within, and synchronized with, external transactions.
+This transaction manager is an implementation of the {spring-framework-java-docs}/transaction/PlatformTransactionManager.html[`PlatformTransactionManager`] interface and should be used with a single Rabbit `ConnectionFactory`.
 
 IMPORTANT: This strategy is not able to provide XA transactions -- for example, in order to share transactions between messaging and database access.
 
 Application code is required to retrieve the transactional Rabbit resources through `ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactory, boolean)` instead of a standard `Connection.createChannel()` call with subsequent channel creation.
-When using Spring AMQP's https://docs.spring.io/spring-amqp/docs/latest_ga/api/org/springframework/amqp/rabbit/core/RabbitTemplate.html[RabbitTemplate], it will autodetect a thread-bound Channel and automatically participate in its transaction.
+When using Spring AMQP's {spring-amqp-java-docs}/rabbit/core/RabbitTemplate.html[RabbitTemplate], it will autodetect a thread-bound Channel and automatically participate in its transaction.
 
 With Java Configuration, you can setup a new RabbitTransactionManager by using the following bean:
 
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/native.adoc b/src/reference/antora/modules/ROOT/pages/appendix/native.adoc
index ee924671a2..8234dc91e6 100644
--- a/src/reference/antora/modules/ROOT/pages/appendix/native.adoc
+++ b/src/reference/antora/modules/ROOT/pages/appendix/native.adoc
@@ -2,6 +2,6 @@
 = Native Images
 :page-section-summary-toc: 1
 
-https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aot[Spring AOT] native hints are provided to assist in developing native images for Spring applications that use Spring AMQP.
+{spring-framework-docs}/core/aot.html[Spring AOT] native hints are provided to assist in developing native images for Spring applications that use Spring AMQP.
 
 Some examples can be seen in the https://github.com/spring-projects/spring-aot-smoke-tests/tree/main/integration[`spring-aot-smoke-tests` GitHub repository].
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-3-since-1-2.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-3-since-1-2.adoc
index da13067fff..41ddf37416 100644
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-3-since-1-2.adoc
+++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-3-since-1-2.adoc
@@ -5,14 +5,14 @@
 == Listener Concurrency
 
 The listener container now supports dynamic scaling of the number of consumers based on workload, or you can programmatically change the concurrency without stopping the container.
-See <>.
+See xref:amqp/listener-concurrency.adoc#listener-concurrency[Listener Concurrency].
 
 [[listener-queues]]
 == Listener Queues
 
 The listener container now permits the queues on which it listens to be modified at runtime.
 Also, the container now starts if at least one of its configured queues is available for use.
-See <>
+See xref:amqp/listener-queues.adoc#listener-queues[Listener Container Queues]
 
 This listener container now redeclares any auto-delete queues during startup.
 See xref:amqp/receiving-messages/async-consumer.adoc#lc-auto-delete[`auto-delete` Queues].
@@ -21,13 +21,13 @@ See xref:amqp/receiving-messages/async-consumer.adoc#lc-auto-delete[`auto-delete
 == Consumer Priority
 
 The listener container now supports consumer arguments, letting the `x-priority` argument be set.
-See <>.
+See xref:amqp/receiving-messages/async-consumer.adoc#consumer-priority[Consumer Priority].
 
 [[exclusive-consumer]]
 == Exclusive Consumer
 
 You can now configure `SimpleMessageListenerContainer` with a single `exclusive` consumer, preventing other consumers from listening to the queue.
-See <>.
+See xref:amqp/exclusive-consumer.adoc[Exclusive Consumer].
 
 [[rabbit-admin]]
 == Rabbit Admin
@@ -47,7 +47,7 @@ If you wish to bind with an empty string routing key, you need to specify `key="
 
 The `AmqpTemplate` now provides several synchronous `receiveAndReply` methods.
 These are implemented by the `RabbitTemplate`.
-For more information see <>.
+For more information see xref:amqp/receiving-messages.adoc[Receiving Messages].
 
 The `RabbitTemplate` now supports configuring a `RetryTemplate` to attempt retries (with optional back-off policy) for when the broker is not available.
 For more information see xref:amqp/template.adoc#template-retry[Adding Retry Capabilities].
@@ -66,12 +66,11 @@ You can now configure the `` of the `` with a `key/va
 These options are mutually exclusive.
 See xref:amqp/broker-configuration.adoc#headers-exchange[Headers Exchange].
 
-[[routing-connection-factory]]
 == Routing Connection Factory
 
 A new `SimpleRoutingConnectionFactory` has been introduced.
 It allows configuration of `ConnectionFactories` mapping, to determine the target `ConnectionFactory` to use at runtime.
-See <>.
+See xref:amqp/connections.adoc#routing-connection-factory[Routing Connection Factory].
 
 [[messagebuilder-and-messagepropertiesbuilder]]
 == `MessageBuilder` and `MessagePropertiesBuilder`
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-4-since-1-3.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-4-since-1-3.adoc
index a4346e4ba4..fef889d846 100644
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-4-since-1-3.adoc
+++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-4-since-1-3.adoc
@@ -35,14 +35,14 @@ See xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and
 
 `RabbitConnectionFactoryBean` creates the underlying RabbitMQ `ConnectionFactory` used by the `CachingConnectionFactory`.
 This enables configuration of SSL options using Spring's dependency injection.
-See <>.
+See xref:amqp/connections.adoc#connection-factory[Configuring the Underlying Client Connection Factory].
 
 [[using-cachingconnectionfactory]]
 == Using `CachingConnectionFactory`
 
 The `CachingConnectionFactory` now lets the `connectionTimeout` be set as a property or as an attribute in the namespace.
 It sets the property on the underlying RabbitMQ `ConnectionFactory`.
-See <>.
+See xref:amqp/connections.adoc#connection-factory[Configuring the Underlying Client Connection Factory].
 
 [[log-appender]]
 == Log Appender
@@ -71,13 +71,13 @@ The `mandatoryExpression`, `sendConnectionFactorySelectorExpression`, and `recei
 The `mandatoryExpression` is used to evaluate a `mandatory` boolean value against each request message when a `ReturnCallback` is in use.
 See xref:amqp/template.adoc#template-confirms[Correlated Publisher Confirms and Returns].
 The `sendConnectionFactorySelectorExpression` and `receiveConnectionFactorySelectorExpression` are used when an `AbstractRoutingConnectionFactory` is provided, to determine the `lookupKey` for the target `ConnectionFactory` at runtime on each AMQP protocol interaction operation.
-See <>.
+See xref:amqp/connections.adoc#routing-connection-factory[Routing Connection Factory].
 
 [[listeners-and-the-routing-connection-factory]]
 == Listeners and the Routing Connection Factory
 
 You can configure a `SimpleMessageListenerContainer` with a routing connection factory to enable connection selection based on the queue names.
-See <>.
+See xref:amqp/connections.adoc#routing-connection-factory[Routing Connection Factory].
 
 [[rabbittemplate:-recoverycallback-option]]
 == `RabbitTemplate`: `RecoveryCallback` Option
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-5-since-1-4.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-5-since-1-4.adoc
index 29282a0033..4b5621cb0e 100644
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-5-since-1-4.adoc
+++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-5-since-1-4.adoc
@@ -5,7 +5,7 @@
 == `spring-erlang` Is No Longer Supported
 
 The `spring-erlang` jar is no longer included in the distribution.
-Use <> instead.
+Use xref:amqp/management-rest-api.adoc#management-rest-api[RabbitMQ REST API] instead.
 
 [[cachingconnectionfactory-changes]]
 == `CachingConnectionFactory` Changes
@@ -122,7 +122,7 @@ See xref:amqp/request-reply.adoc#reply-listener[Reply Listener Container] for mo
 == `RabbitManagementTemplate` Added
 
 The `RabbitManagementTemplate` has been introduced to monitor and configure the RabbitMQ Broker by using the REST API provided by its https://www.rabbitmq.com/management.html[management plugin].
-See <> for more information.
+See xref:amqp/management-rest-api.adoc#management-rest-api[RabbitMQ REST API] for more information.
 
 [[listener-container-bean-names-xml]]
 == Listener Container Bean Names (XML)
@@ -160,7 +160,7 @@ See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration]
 == Channel Close Logging
 
 A mechanism to control the log levels of channel closure has been introduced.
-See <>.
+See xref:amqp/connections.adoc#channel-close-logging[Logging Channel Close Events].
 
 [[application-events]]
 == Application Events
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-6-since-1-5.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-6-since-1-5.adoc
index 02d8037f46..2e96f8fea2 100644
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-6-since-1-5.adoc
+++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-6-since-1-5.adoc
@@ -189,7 +189,7 @@ See xref:amqp/receiving-messages/async-annotation-driven.adoc[Annotation-driven
 == Delayed Message Exchange
 
 Spring AMQP now has first class support for the RabbitMQ Delayed Message Exchange plugin.
-See <> for more information.
+See xref:amqp/delayed-message-exchange.adoc[Delayed Message Exchange] for more information.
 
 [[exchange-internal-flag]]
 == Exchange Internal Flag
@@ -235,7 +235,7 @@ factory.
 You can now configure a "`allowed list`" of allowable classes when you use Java deserialization.
 You should consider creating an allowed list if you accept messages with serialized java objects from
 untrusted sources.
-See <> for more information.
+See amqp/message-converters.adoc#java-deserialization[Java Deserialization] for more information.
 
 [[json-messageconverter]]
 == JSON `MessageConverter`
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-7-since-1-6.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-7-since-1-6.adoc
index 2c565b3025..054dd56e42 100644
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-7-since-1-6.adoc
+++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-1-7-since-1-6.adoc
@@ -51,7 +51,7 @@ The framework is no longer compatible with previous versions.
 == JUnit `@Rules`
 
 Rules that have previously been used internally by the framework have now been made available in a separate jar called `spring-rabbit-junit`.
-See <> for more information.
+See xref:testing.adoc#junit-rules[JUnit4 `@Rules`] for more information.
 
 [[container-conditional-rollback]]
 == Container Conditional Rollback
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-0-since-1-7.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-0-since-1-7.adoc
index 9355130498..92fd10598b 100644
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-0-since-1-7.adoc
+++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-0-since-1-7.adoc
@@ -68,7 +68,7 @@ See xref:amqp/request-reply.adoc#async-template[Async Rabbit Template] for more
 The `RabbitTemplate` and `AsyncRabbitTemplate` now have `receiveAndConvert` and `convertSendAndReceiveAsType` methods that take a `ParameterizedTypeReference` argument, letting the caller specify the type to which to convert the result.
 This is particularly useful for complex types or when type information is not conveyed in message headers.
 It requires a `SmartMessageConverter` such as the `Jackson2JsonMessageConverter`.
-See xref:amqp/request-reply.adoc[Request/Reply Messaging], xref:amqp/request-reply.adoc#async-template[Async Rabbit Template], xref:amqp/message-converters.adoc#json-complex[Converting From a `Message` With `RabbitTemplate`], and <> for more information.
+See xref:amqp/request-reply.adoc[Request/Reply Messaging], xref:amqp/request-reply.adoc#async-template[Async Rabbit Template], xref:amqp/message-converters.adoc#json-complex[Converting From a `Message` With `RabbitTemplate`], and xref:amqp/message-converters.adoc#json-complex[Converting From a `Message` With `RabbitTemplate`] for more information.
 
 You can now use a `RabbitTemplate` to perform multiple operations on a dedicated channel.
 See xref:amqp/template.adoc#scoped-operations[Scoped Operations] for more information.
@@ -179,7 +179,7 @@ See xref:amqp/transactions.adoc#conditional-rollback[Conditional Rollback] for m
 
 Deprecated in previous versions, Jackson `1.x` converters and related components have now been deleted.
 You can use similar components based on Jackson 2.x.
-See <> for more information.
+See xref:amqp/message-converters.adoc#json-message-converter[`Jackson2JsonMessageConverter`] for more information.
 
 [[json-message-converter]]
 == JSON Message Converter
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-1-since-2-0.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-1-since-2-0.adoc
index a905c1899b..07f3bacc9c 100644
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-1-since-2-0.adoc
+++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-1-since-2-0.adoc
@@ -81,7 +81,7 @@ See xref:amqp/message-converters.adoc#jackson2xml[`Jackson2XmlMessageConverter`]
 == Management REST API
 
 The `RabbitManagementTemplate` is now deprecated in favor of the direct `com.rabbitmq.http.client.Client` (or `com.rabbitmq.http.client.ReactorNettyClient`) usage.
-See <> for more information.
+See xref:amqp/management-rest-api.adoc#management-rest-api[RabbitMQ REST API] for more information.
 
 [[rabbitlistener-changes]]
 == `@RabbitListener` Changes
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-3-since-2-2.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-3-since-2-2.adoc
index d49bf3128f..fca45e9164 100644
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-3-since-2-2.adoc
+++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-3-since-2-2.adoc
@@ -43,7 +43,7 @@ See xref:amqp/request-reply.adoc#direct-reply-to[RabbitMQ Direct reply-to] for m
 [[listener-container-changes]]
 == Listener Container Changes
 
-A new listener container property `consumeDelay` is now available; it is helpful when using the https://github.com/rabbitmq/rabbitmq-sharding[RabbitMQ Sharding Plugin].
+A new listener container property `consumeDelay` is now available; it is helpful when using the {rabbitmq-server-github}/rabbitmq_sharding[RabbitMQ Sharding Plugin].
 
 The default `JavaLangErrorHandler` now calls `System.exit(99)`.
 To revert to the previous behavior (do nothing), add a no-op handler.
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-4-since-2-3.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-4-since-2-3.adoc
index 0dc4bf3fcc..372f46fcff 100644
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-4-since-2-3.adoc
+++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-in-2-4-since-2-3.adoc
@@ -22,3 +22,15 @@ See xref:amqp/broker-configuration.adoc#declarable-recovery[Recovering Auto-Dele
 
 Support remoting using Spring Framework’s RMI support is deprecated and will be removed in 3.0. See Spring Remoting with AMQP for more information.
 
+[[stream-support-changes]]
+== Stream Support Changes
+
+`RabbitStreamOperations` and `RabbitStreamTemplate` have been deprecated in favor of `RabbitStreamOperations2` and `RabbitStreamTemplate2` respectively; they return `CompletableFuture` instead of `ListenableFuture`.
+See xref:stream.adoc[Using the RabbitMQ Stream Plugin] for more information.
+
+[[message-converter-changes]]
+== Message Converter Changes
+
+The `Jackson2JsonMessageConverter` can now determine the charset from the `contentEncoding` header.
+See xref:amqp/message-converters.adoc#json-message-converter[`Jackson2JsonMessageConverter`] for more information.
+
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-to-1-2-since-1-1.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-to-1-2-since-1-1.adoc
index 17410e9823..4180c54d78 100644
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-to-1-2-since-1-1.adoc
+++ b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/changes-to-1-2-since-1-1.adoc
@@ -47,7 +47,7 @@ See xref:amqp/broker-configuration.adoc#conditional-declaration[Conditional Decl
 == AMQP Remoting
 
 Facilities are now provided for using Spring remoting techniques, using AMQP as the transport for the RPC calls.
-For more information see <>
+For more information see xref:amqp/request-reply.adoc#remoting[Spring Remoting with AMQP].
 
 [[requested-heart-beats]]
 == Requested Heart Beats
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/earlier-releases.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/earlier-releases.adoc
deleted file mode 100644
index d92f5b10f6..0000000000
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/earlier-releases.adoc
+++ /dev/null
@@ -1,6 +0,0 @@
-[[earlier-releases]]
-= Earlier Releases
-:page-section-summary-toc: 1
-
-See xref:appendix/previous-whats-new.adoc[Previous Releases] for changes in previous versions.
-
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/message-converter-changes.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/message-converter-changes.adoc
deleted file mode 100644
index 693a71cf40..0000000000
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/message-converter-changes.adoc
+++ /dev/null
@@ -1,7 +0,0 @@
-[[message-converter-changes]]
-= Message Converter Changes
-:page-section-summary-toc: 1
-
-The `Jackson2JsonMessageConverter` can now determine the charset from the `contentEncoding` header.
-See xref:amqp/message-converters.adoc#json-message-converter[`Jackson2JsonMessageConverter`] for more information.
-
diff --git a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/stream-support-changes.adoc b/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/stream-support-changes.adoc
deleted file mode 100644
index b6be8ec44a..0000000000
--- a/src/reference/antora/modules/ROOT/pages/appendix/previous-whats-new/stream-support-changes.adoc
+++ /dev/null
@@ -1,7 +0,0 @@
-[[stream-support-changes]]
-= Stream Support Changes
-:page-section-summary-toc: 1
-
-`RabbitStreamOperations` and `RabbitStreamTemplate` have been deprecated in favor of `RabbitStreamOperations2` and `RabbitStreamTemplate2` respectively; they return `CompletableFuture` instead of `ListenableFuture`.
-See xref:stream.adoc[Using the RabbitMQ Stream Plugin] for more information.
-
diff --git a/src/reference/antora/modules/ROOT/pages/integration-reference.adoc b/src/reference/antora/modules/ROOT/pages/integration-reference.adoc
index 77745b8955..1dbc762022 100644
--- a/src/reference/antora/modules/ROOT/pages/integration-reference.adoc
+++ b/src/reference/antora/modules/ROOT/pages/integration-reference.adoc
@@ -13,7 +13,7 @@ We provide an inbound-channel-adapter, an outbound-channel-adapter, an inbound-g
 
 Since the AMQP adapters are part of the Spring Integration release, the documentation is available as part of the Spring Integration distribution.
 We provide a quick overview of the main features here.
-See the https://docs.spring.io/spring-integration/reference[Spring Integration Reference Guide] for much more detail.
+See the {spring-integration-docs}[Spring Integration Reference Guide] for much more detail.
 
 [[inbound-channel-adapter]]
 == Inbound Channel Adapter
diff --git a/src/reference/antora/modules/ROOT/pages/logging.adoc b/src/reference/antora/modules/ROOT/pages/logging.adoc
index 21b6855381..cf9b0ff0e5 100644
--- a/src/reference/antora/modules/ROOT/pages/logging.adoc
+++ b/src/reference/antora/modules/ROOT/pages/logging.adoc
@@ -395,7 +395,7 @@ public class MyEnhancedAppender extends AmqpAppender {
 }
 ----
 
-The Log4j 2 appender supports using a https://logging.apache.org/log4j/2.x/manual/appenders.html#BlockingQueueFactory[`BlockingQueueFactory`], as the following example shows:
+The Log4j 2 appender supports using a https://logging.apache.org/log4j/2.x/manual/appenders/delegating.html#BlockingQueueFactory[`BlockingQueueFactory`], as the following example shows:
 
 [source, xml]
 ----
diff --git a/src/reference/antora/modules/ROOT/pages/sample-apps.adoc b/src/reference/antora/modules/ROOT/pages/sample-apps.adoc
index bf983d5edc..90a2775e53 100644
--- a/src/reference/antora/modules/ROOT/pages/sample-apps.adoc
+++ b/src/reference/antora/modules/ROOT/pages/sample-apps.adoc
@@ -20,7 +20,7 @@ You can import the `spring-rabbit-helloworld` sample into the IDE and then follo
 Within the `src/main/java` directory, navigate to the `org.springframework.amqp.helloworld` package.
 Open the `HelloWorldConfiguration` class and notice that it contains the `@Configuration` annotation at the class level and notice some `@Bean` annotations at method-level.
 This is an example of Spring's Java-based configuration.
-You can read more about that https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-java[here].
+You can read more about that {spring-framework-docs}/core/beans/java.html[here].
 
 The following listing shows how the connection factory is created:
 
@@ -144,7 +144,7 @@ static class ScheduledProducer {
 ----
 
 You do not need to understand all of the details, since the real focus should be on the receiving side (which we cover next).
-However, if you are not yet familiar with Spring task scheduling support, you can learn more https://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html#scheduling-annotation-support[here].
+However, if you are not yet familiar with Spring task scheduling support, you can learn more {spring-framework-docs}/integration/scheduling.html#scheduling-annotation-support-scheduled[here].
 The short story is that the `postProcessor` bean in the `ProducerConfiguration` registers the task with a scheduler.
 
 Now we can turn to the receiving side.
@@ -351,4 +351,4 @@ Spring applications, when sending JSON, set the `__TypeId__` header to the fully
 
 The `spring-rabbit-json` sample explores several techniques to convert the JSON from a non-Spring application.
 
-See also xref:amqp/message-converters.adoc#json-message-converter[`Jackson2JsonMessageConverter`] as well as the https://docs.spring.io/spring-amqp/docs/current/api/index.html?org/springframework/amqp/support/converter/DefaultClassMapper.html[Javadoc for the `DefaultClassMapper`].
+See also xref:amqp/message-converters.adoc#json-message-converter[`Jackson2JsonMessageConverter`] as well as the {spring-amqp-java-docs}/index.html?org/springframework/amqp/support/converter/DefaultClassMapper.html[Javadoc for the `DefaultClassMapper`].
diff --git a/src/reference/antora/modules/ROOT/pages/stream.adoc b/src/reference/antora/modules/ROOT/pages/stream.adoc
index 4994354fca..b1e35babbf 100644
--- a/src/reference/antora/modules/ROOT/pages/stream.adoc
+++ b/src/reference/antora/modules/ROOT/pages/stream.adoc
@@ -1,7 +1,7 @@
 [[stream-support]]
 = Using the RabbitMQ Stream Plugin
 
-Version 2.4 introduces initial support for the https://github.com/rabbitmq/rabbitmq-stream-java-client[RabbitMQ Stream Plugin Java Client] for the https://rabbitmq.com/stream.html[RabbitMQ Stream Plugin].
+Version 2.4 introduces initial support for the {rabbitmq-github}/rabbitmq-stream-java-client[RabbitMQ Stream Plugin Java Client] for the https://rabbitmq.com/stream.html[RabbitMQ Stream Plugin].
 
 * `RabbitStreamTemplate`
 * `StreamListenerContainer`
@@ -109,9 +109,7 @@ You can also send native stream `Message` s directly; with the `messageBuilder()
 
 The `ProducerCustomizer` provides a mechanism to customize the producer before it is built.
 
-Refer to the https://rabbitmq.github.io/rabbitmq-stream-java-client/stable/htmlsingle/[Java Client Documentation] about customizing the `Environment` and `Producer`.
-
-IMPORTANT: Starting with version 3.0, the method return types are `CompletableFuture` instead of `ListenableFuture`.
+Refer to the {rabbitmq-stream-docs}[Java Client Documentation] about customizing the `Environment` and `Producer`.
 
 [[receiving-messages]]
 == Receiving Messages
@@ -135,7 +133,7 @@ See xref:amqp/containerAttributes.adoc[Message Listener Container Configuration]
 
 Similar the template, the container has a `ConsumerCustomizer` property.
 
-Refer to the https://rabbitmq.github.io/rabbitmq-stream-java-client/stable/htmlsingle/[Java Client Documentation] about customizing the `Environment` and `Consumer`.
+Refer to the {rabbitmq-stream-docs}[Java Client Documentation] about customizing the `Environment` and `Consumer`.
 
 When using `@RabbitListener`, configure a `StreamRabbitListenerContainerFactory`; at this time, most `@RabbitListener` properties (`concurrency`, etc) are ignored. Only `id`, `queues`, `autoStartup` and `containerFactory` are supported.
 In addition, `queues` can only contain one stream name.
@@ -287,7 +285,7 @@ StreamListenerContainer container(Environment env, String name) {
 ----
 
 IMPORTANT: At this time, when the concurrency is greater than 1, the actual concurrency is further controlled by the `Environment`; to achieve full concurrency, set the environment's `maxConsumersByConnection` to 1.
-See https://rabbitmq.github.io/rabbitmq-stream-java-client/snapshot/htmlsingle/#configuring-the-environment[Configuring the Environment].
+See {rabbitmq-stream-docs}/#configuring-the-environment[Configuring the Environment].
 
 [[stream-micrometer-observation]]
 == Micrometer Observation
@@ -298,7 +296,7 @@ The container now also supports Micrometer timers (when observation is not enabl
 Set `observationEnabled` on each component to enable observation; this will disable xref:amqp/receiving-messages/micrometer.adoc[Micrometer Timers] because the timers will now be managed with each observation.
 When using annotated listeners, set `observationEnabled` on the container factory.
 
-Refer to https://docs.micrometer.io/tracing/reference/[Micrometer Tracing] for more information.
+Refer to {micrometer-tracing-docs}[Micrometer Tracing] for more information.
 
 To add tags to timers/traces, configure a custom `RabbitStreamTemplateObservationConvention` or `RabbitStreamListenerObservationConvention` to the template or listener container, respectively.
 
diff --git a/src/reference/antora/modules/ROOT/pages/whats-new.adoc b/src/reference/antora/modules/ROOT/pages/whats-new.adoc
index 3258c28f44..0e643ec6ca 100644
--- a/src/reference/antora/modules/ROOT/pages/whats-new.adoc
+++ b/src/reference/antora/modules/ROOT/pages/whats-new.adoc
@@ -13,4 +13,9 @@ This version requires Spring Framework 6.2.
 [[x32-consistent-hash-exchange]]
 === Consistent Hash Exchange
 
-The convenient `ConsistentHashExchange` and respective `ExchangeBuilder.consistentHashExchange()` API has been introduced.
\ No newline at end of file
+The convenient `ConsistentHashExchange` and respective `ExchangeBuilder.consistentHashExchange()` API has been introduced.
+
+[[x32-retry-count-header]]
+=== The `retry_count` header
+
+The `retry_count` header should be used now instead of relying on server side increment for the `x-death.count` property.
\ No newline at end of file