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

merlimat pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar.git


The following commit(s) were added to refs/heads/master by this push:
     new cf0c24e6723 [improve][build] Fix publishing of public libraries with 
correct POM and Gradle module metadata (#26020)
cf0c24e6723 is described below

commit cf0c24e67238700ca9de49d4a765a8b016b87af4
Author: Lari Hotari <[email protected]>
AuthorDate: Sun Jun 14 02:59:57 2026 +0300

    [improve][build] Fix publishing of public libraries with correct POM and 
Gradle module metadata (#26020)
---
 .github/workflows/pulsar-ci.yaml                   |   3 +
 bouncy-castle/bc/LICENSE                           | 209 --------------------
 bouncy-castle/bc/build.gradle.kts                  |  31 ---
 .../apache/pulsar/bcloader/BouncyCastleLoader.java |  46 -----
 .../org/apache/pulsar/bcloader/package-info.java   |  23 ---
 .../resources/META-INF/services/bouncy-castle.yaml |  22 ---
 bouncy-castle/bcfips/LICENSE                       | 210 ---------------------
 bouncy-castle/bcfips/build.gradle.kts              |  32 ----
 .../pulsar/bcloader/BouncyCastleFipsLoader.java    |  46 -----
 .../org/apache/pulsar/bcloader/package-info.java   |  19 --
 .../resources/META-INF/services/bouncy-castle.yaml |  22 ---
 .../src/main/kotlin/CheckBinaryLicenseTask.kt      |   1 -
 .../main/kotlin/pulsar.java-conventions.gradle.kts |  91 ++++++---
 .../main/kotlin/pulsar.nar-conventions.gradle.kts  |   1 -
 ...lsar.public-java-library-conventions.gradle.kts |   2 +-
 .../kotlin/pulsar.publish-conventions.gradle.kts   |  94 ++++++---
 buildtools/build.gradle.kts                        |   2 +-
 distribution/server/build.gradle.kts               |  18 +-
 distribution/shell/build.gradle.kts                |  21 +--
 managed-ledger/build.gradle.kts                    |  10 +-
 microbench/build.gradle.kts                        |   8 +-
 pulsar-bom/build.gradle.kts                        |   4 -
 pulsar-broker-auth-athenz/build.gradle.kts         |   2 +-
 pulsar-broker-auth-oidc/build.gradle.kts           |   4 +-
 pulsar-broker-auth-sasl/build.gradle.kts           |   4 +-
 pulsar-broker-common/build.gradle.kts              |  23 ++-
 pulsar-broker/build.gradle.kts                     |  50 ++---
 pulsar-cli-utils/build.gradle.kts                  |   2 +-
 pulsar-client-admin-api/build.gradle.kts           |   2 +-
 pulsar-client-admin-shaded/build.gradle.kts        |  15 ++
 pulsar-client-admin/build.gradle.kts               |  14 +-
 pulsar-client-all/build.gradle.kts                 |  17 +-
 pulsar-client-api-v5/build.gradle.kts              |   2 +-
 pulsar-client-auth-athenz/build.gradle.kts         |   2 +-
 pulsar-client-messagecrypto-bc/build.gradle.kts    |  10 +-
 .../pulsar/client/impl/crypto/MessageCryptoBc.java |  80 +++++---
 pulsar-client-shaded/build.gradle.kts              |  19 +-
 pulsar-client-tools/build.gradle.kts               |  12 +-
 pulsar-client-v5/build.gradle.kts                  |   4 +-
 pulsar-client/build.gradle.kts                     |  18 +-
 pulsar-common/build.gradle.kts                     |  29 +--
 .../org/apache/pulsar/common/util/BCLoader.java    |  28 ---
 pulsar-docs-tools/build.gradle.kts                 |   4 +-
 pulsar-functions/instance/build.gradle.kts         |  22 +--
 .../functions/instance/ProducerBuilderFactory.java |   7 -
 .../pulsar/functions/source/PulsarSource.java      |   7 -
 pulsar-functions/localrun/build.gradle.kts         |   2 +-
 pulsar-functions/runtime/build.gradle.kts          |  10 +-
 pulsar-functions/secrets/build.gradle.kts          |   2 +-
 pulsar-functions/utils/build.gradle.kts            |   4 +-
 pulsar-functions/worker/build.gradle.kts           |  14 +-
 pulsar-io/batch-data-generator/build.gradle.kts    |   4 +-
 pulsar-io/data-generator/build.gradle.kts          |   4 +-
 pulsar-metadata/build.gradle.kts                   |   4 +-
 pulsar-opentelemetry/build.gradle.kts              |   2 +-
 pulsar-proxy/build.gradle.kts                      |  24 +--
 pulsar-testclient/build.gradle.kts                 |  18 +-
 pulsar-transaction/coordinator/build.gradle.kts    |   8 +-
 pulsar-websocket/build.gradle.kts                  |   8 +-
 settings.gradle.kts                                |   9 +-
 .../java-test-functions/build.gradle.kts           |   4 +-
 .../pulsar-client-all-shade-test/build.gradle.kts  |   2 +-
 .../pulsar-client-test-bcfips}/build.gradle.kts    |  12 +-
 .../pulsar/client/TlsProducerConsumerBase.java     |   0
 .../pulsar/client/TlsProducerConsumerTest.java     |   0
 tiered-storage/file-system/build.gradle.kts        |   2 +-
 66 files changed, 436 insertions(+), 989 deletions(-)

diff --git a/.github/workflows/pulsar-ci.yaml b/.github/workflows/pulsar-ci.yaml
index 17d88719689..3a7a94f0559 100644
--- a/.github/workflows/pulsar-ci.yaml
+++ b/.github/workflows/pulsar-ci.yaml
@@ -178,6 +178,9 @@ jobs:
       - name: Check binary licenses
         run: ./gradlew checkBinaryLicense --no-configuration-cache
 
+      - name: Check that project's public libraries can be published to a 
Maven repository
+        run: ./gradlew publishAllPublicationsToLocalDeployRepository
+
       - name: Upload Gradle reports
         uses: actions/upload-artifact@v4
         if: ${{ !success() }}
diff --git a/bouncy-castle/bc/LICENSE b/bouncy-castle/bc/LICENSE
deleted file mode 100644
index 9cbf445cda2..00000000000
--- a/bouncy-castle/bc/LICENSE
+++ /dev/null
@@ -1,209 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-----------------------------------------------------------------------------------------------------
-
-This projects includes binary packages with the following licenses:
-Bouncy Castle License
- * Bouncy Castle -- licenses/LICENSE-bouncycastle.txt
-    - org.bouncycastle-bcpkix-jdk18on-1.81.jar
-    - org.bouncycastle-bcprov-jdk18on-1.78.1.jar
diff --git a/bouncy-castle/bc/build.gradle.kts 
b/bouncy-castle/bc/build.gradle.kts
deleted file mode 100644
index 9e2527691c2..00000000000
--- a/bouncy-castle/bc/build.gradle.kts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-plugins {
-    id("pulsar.public-java-library-conventions")
-}
-
-dependencies {
-    implementation(libs.slog)
-    compileOnly(project(":pulsar-common")) {
-        exclude(group = "io.prometheus", module = "simpleclient_caffeine")
-    }
-    implementation(libs.bcpkix.jdk18on)
-    implementation(libs.bcprov.jdk18on)
-}
diff --git 
a/bouncy-castle/bc/src/main/java/org/apache/pulsar/bcloader/BouncyCastleLoader.java
 
b/bouncy-castle/bc/src/main/java/org/apache/pulsar/bcloader/BouncyCastleLoader.java
deleted file mode 100644
index f1f452b7d7e..00000000000
--- 
a/bouncy-castle/bc/src/main/java/org/apache/pulsar/bcloader/BouncyCastleLoader.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.pulsar.bcloader;
-
-import static org.apache.pulsar.common.util.SecurityUtility.BC;
-import java.security.Provider;
-import java.security.Security;
-import lombok.CustomLog;
-import org.apache.pulsar.common.util.BCLoader;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-
-/**
- * This is a Bouncy Castle provider Loader.
- */
-@CustomLog
-public class BouncyCastleLoader implements BCLoader {
-    public static Provider provider;
-    static {
-        if (Security.getProvider(BC) == null) {
-            Security.addProvider(new BouncyCastleProvider());
-        }
-        provider = Security.getProvider(BC);
-        log.info().attr("provider", provider).log("BouncyCastle Provider BC 
loaded");
-    }
-
-    @Override
-    public Provider getProvider() {
-        return Security.getProvider(BC);
-    }
-}
diff --git 
a/bouncy-castle/bc/src/main/java/org/apache/pulsar/bcloader/package-info.java 
b/bouncy-castle/bc/src/main/java/org/apache/pulsar/bcloader/package-info.java
deleted file mode 100644
index da465105f14..00000000000
--- 
a/bouncy-castle/bc/src/main/java/org/apache/pulsar/bcloader/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Package for Bouncy Castle provider Loader.
- */
-package org.apache.pulsar.bcloader;
diff --git 
a/bouncy-castle/bc/src/main/resources/META-INF/services/bouncy-castle.yaml 
b/bouncy-castle/bc/src/main/resources/META-INF/services/bouncy-castle.yaml
deleted file mode 100644
index 5f8217c1ade..00000000000
--- a/bouncy-castle/bc/src/main/resources/META-INF/services/bouncy-castle.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-name: bouncy-castle
-description: loader for Bouncy Castle provider
-bcLoaderClass: org.apache.pulsar.bcloader.BouncyCastleLoader
diff --git a/bouncy-castle/bcfips/LICENSE b/bouncy-castle/bcfips/LICENSE
deleted file mode 100644
index 5eda282e5aa..00000000000
--- a/bouncy-castle/bcfips/LICENSE
+++ /dev/null
@@ -1,210 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-----------------------------------------------------------------------------------------------------
-
-This projects includes binary packages with the following licenses:
-Bouncy Castle License
- * Bouncy Castle -- licenses/LICENSE-bouncycastle.txt
-    - org.bouncycastle-bcpkix-fips-2.0.10.jar
-    - org.bouncycastle-bc-fips-2.0.1.jar
-    - org.bouncycastle-bctutil-fips-2.0.5.jar
diff --git a/bouncy-castle/bcfips/build.gradle.kts 
b/bouncy-castle/bcfips/build.gradle.kts
deleted file mode 100644
index d722c3f4561..00000000000
--- a/bouncy-castle/bcfips/build.gradle.kts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-plugins {
-    id("pulsar.public-java-library-conventions")
-}
-
-dependencies {
-    implementation(libs.slog)
-    compileOnly(project(":pulsar-common")) {
-        exclude(group = "io.prometheus", module = "simpleclient_caffeine")
-    }
-    implementation(libs.bcutil.fips)
-    implementation(libs.bc.fips)
-    implementation(libs.bcpkix.fips)
-}
diff --git 
a/bouncy-castle/bcfips/src/main/java/org/apache/pulsar/bcloader/BouncyCastleFipsLoader.java
 
b/bouncy-castle/bcfips/src/main/java/org/apache/pulsar/bcloader/BouncyCastleFipsLoader.java
deleted file mode 100644
index 842c9537bd2..00000000000
--- 
a/bouncy-castle/bcfips/src/main/java/org/apache/pulsar/bcloader/BouncyCastleFipsLoader.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.pulsar.bcloader;
-
-import static org.apache.pulsar.common.util.SecurityUtility.BC_FIPS;
-import java.security.Provider;
-import java.security.Security;
-import lombok.CustomLog;
-import org.apache.pulsar.common.util.BCLoader;
-import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
-
-/**
- * This is a Bouncy Castle provider Loader.
- */
-@CustomLog
-public class BouncyCastleFipsLoader implements BCLoader {
-    public static Provider provider;
-    static {
-        if (Security.getProvider(BC_FIPS) == null) {
-            Security.addProvider(new BouncyCastleFipsProvider());
-        }
-        provider = Security.getProvider(BC_FIPS);
-        log.info().attr("provider", 
Security.getProvider(BC_FIPS)).log("BouncyCastle Provider BC_FIPS loaded");
-    }
-
-    @Override
-    public Provider getProvider() {
-        return Security.getProvider(BC_FIPS);
-    }
-}
diff --git 
a/bouncy-castle/bcfips/src/main/java/org/apache/pulsar/bcloader/package-info.java
 
b/bouncy-castle/bcfips/src/main/java/org/apache/pulsar/bcloader/package-info.java
deleted file mode 100644
index d610ff5642d..00000000000
--- 
a/bouncy-castle/bcfips/src/main/java/org/apache/pulsar/bcloader/package-info.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.pulsar.bcloader;
diff --git 
a/bouncy-castle/bcfips/src/main/resources/META-INF/services/bouncy-castle.yaml 
b/bouncy-castle/bcfips/src/main/resources/META-INF/services/bouncy-castle.yaml
deleted file mode 100644
index 26ca9cde659..00000000000
--- 
a/bouncy-castle/bcfips/src/main/resources/META-INF/services/bouncy-castle.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-name: bouncy-castle-fips
-description: loader for Bouncy Castle FIPS provider
-bcLoaderClass: org.apache.pulsar.bcloader.BouncyCastleFipsLoader
diff --git a/build-logic/conventions/src/main/kotlin/CheckBinaryLicenseTask.kt 
b/build-logic/conventions/src/main/kotlin/CheckBinaryLicenseTask.kt
index 4be5bd78759..e52ec7bbe65 100644
--- a/build-logic/conventions/src/main/kotlin/CheckBinaryLicenseTask.kt
+++ b/build-logic/conventions/src/main/kotlin/CheckBinaryLicenseTask.kt
@@ -67,7 +67,6 @@ abstract class CheckBinaryLicenseTask : DefaultTask() {
             "pulsar-common",
             "pulsar-package",
             "pulsar-websocket",
-            "bouncy-castle-bc",
         )
 
         val bundledJars = sortedSetOf<String>()
diff --git 
a/build-logic/conventions/src/main/kotlin/pulsar.java-conventions.gradle.kts 
b/build-logic/conventions/src/main/kotlin/pulsar.java-conventions.gradle.kts
index a63dbb3ca14..51f1e2a484a 100644
--- a/build-logic/conventions/src/main/kotlin/pulsar.java-conventions.gradle.kts
+++ b/build-logic/conventions/src/main/kotlin/pulsar.java-conventions.gradle.kts
@@ -24,6 +24,45 @@ plugins {
 
 val catalog = the<VersionCatalogsExtension>().named("libs")
 
+// Versions for dependencies injected by the component-metadata rules below, 
captured as plain
+// strings so the rule closures stay configuration-cache compatible (and so 
the injected deps carry
+// explicit versions rather than relying on the alignment platform being on 
every classpath).
+val jakartaActivationApiVersion = 
catalog.findVersion("jakarta-activation").get().requiredVersion
+val angusActivationVersion = 
catalog.findVersion("angus-activation").get().requiredVersion
+
+// Internal version-alignment platform bucket.
+// The enforced platform (:pulsar-dependencies) must align the build's OWN 
resolution but must NOT
+// leak into the published apiElements/runtimeElements variants: an 
enforcedPlatform recorded in
+// published Gradle Module Metadata forces Pulsar's internal versions onto 
downstream Gradle
+// consumers and strips their ability to override (Gradle's platforms docs 
warn against using
+// enforcedPlatform on a component intended for consumption). Declaring it on 
a non-consumable,
+// non-resolvable bucket that only the resolvable build classpaths extend 
keeps full build-time
+// alignment (and feeds versionMapping for POM versions) while leaving it out 
of publication.
+// Consumers get the separate, non-enforced pulsar-bom for version 
recommendations instead.
+val internalPlatform = configurations.create("internalPlatform") {
+    isCanBeConsumed = false
+    isCanBeResolved = false
+    description = "Enforced version-alignment platform for build resolution 
only; never published."
+}
+// Wire the alignment platform onto the build's resolvable classpaths by name. 
These configurations
+// are created by the Java plugin before this convention's body runs, so the 
name match reliably
+// fires for them. (Matching on the mutable isCanBeResolved/isCanBeConsumed 
flags is NOT reliable for
+// configurations created later via `configurations.creating {}` — their flags 
are set inside the
+// creation block, after a flag-based configureEach predicate has already 
evaluated the legacy
+// defaults.) The consumable variants apiElements/runtimeElements are 
intentionally not listed, so
+// the enforced platform stays out of published Gradle Module Metadata. Custom 
resolvable
+// configurations that collect artifacts and need the same alignment (notably 
the distributions'
+// `distLib`, which feeds the binary distribution checked by 
checkBinaryLicense) extend
+// `internalPlatform` explicitly where they are declared.
+val platformAlignedClasspaths = setOf(
+    "compileClasspath", "runtimeClasspath",
+    "testCompileClasspath", "testRuntimeClasspath",
+    "annotationProcessor", "testAnnotationProcessor",
+)
+configurations.matching { it.name in platformAlignedClasspaths }.configureEach 
{
+    extendsFrom(internalPlatform)
+}
+
 tasks.withType<JavaCompile>().configureEach {
     options.encoding = "UTF-8"
     options.release.set(17)
@@ -31,11 +70,6 @@ tasks.withType<JavaCompile>().configureEach {
 }
 
 configurations.all {
-    // Exclude the old SLF4J 1.x bridge pulled in by bookkeeper-server.
-    // Pulsar uses SLF4J 2.x with log4j-slf4j2-impl; having both causes
-    // NoSuchMethodError in Log4jLoggerFactory at test startup.
-    exclude(group = "org.apache.logging.log4j", module = "log4j-slf4j-impl")
-
     // Force Jackson version to match the version catalog. Transitive 
dependencies
     // (e.g. from jackson-bom) can pull in newer versions that break API 
compatibility
     // (EnumResolver.constructUsingToString signature changed in 2.19+).
@@ -50,28 +84,24 @@ configurations.all {
     }
 }
 
-// Exclude bc-fips from modules that don't need it. bc-fips's 
CryptoServicesRegistrar
-// conflicts with bcprov-jdk18on's version — having both causes 
NoSuchMethodError.
-// Only the FIPS-specific modules and modules with explicit FIPS tests should 
have it.
-val modulesUsingBcFips = setOf(
-    "bcfips", "bcfips-include-test",
-    "pulsar-common", "pulsar-broker-common",
-)
-if (project.name !in modulesUsingBcFips) {
-    configurations.all {
-        exclude(group = "org.bouncycastle", module = "bc-fips")
-    }
-}
-
 dependencies {
-    // Exclude all BouncyCastle from bookkeeper-server (matches Maven parent 
POM exclusion).
-    // BookKeeper's bc-fips transitive dependency contains a 
CryptoServicesRegistrar that
-    // conflicts with the non-FIPS version in bcprov-jdk18on. Pulsar manages 
its own BC deps.
+    // Strip conflicting transitive dependencies from bookkeeper-server at the 
source. Handling them
+    // here (rather than with a build-wide `configurations.all { exclude(...) 
}`) keeps them off the
+    // classpath without leaking a per-dependency <exclusion> onto every 
dependency in every
+    // published POM.
+    //   - All BouncyCastle: BookKeeper's transitive bc-fips bundles a 
CryptoServicesRegistrar that
+    //     conflicts with the non-FIPS bcprov-jdk18on. Pulsar manages its own 
BC deps; the FIPS
+    //     modules (bcfips, pulsar-common/pulsar-broker-common tests) declare 
bc-fips directly.
+    //   - log4j-slf4j-impl: the SLF4J 1.x bridge conflicts with Pulsar's 
SLF4J 2.x setup
+    //     (log4j-slf4j2-impl), causing NoSuchMethodError in 
Log4jLoggerFactory at startup.
     components {
         withModule("org.apache.bookkeeper:bookkeeper-server") {
             allVariants {
                 withDependencies {
-                    removeAll { it.group == "org.bouncycastle" }
+                    removeAll {
+                        it.group == "org.bouncycastle" ||
+                            (it.group == "org.apache.logging.log4j" && it.name 
== "log4j-slf4j-impl")
+                    }
                 }
             }
         }
@@ -80,14 +110,15 @@ dependencies {
         // jakarta.activation:jakarta.activation-api 2.1.x. Replace it 
everywhere with the API artifact
         // plus the Eclipse Angus implementation (the EE10 successor). 
async-http-client still depends
         // on it 
(https://github.com/AsyncHttpClient/async-http-client/issues/2190) and this 
rule also
-        // guards against any future dependency pulling it in. Versions are 
pinned by the
-        // pulsar-dependencies platform.
+        // guards against any future dependency pulling it in. The 
replacements carry explicit catalog
+        // versions so they resolve on any classpath without relying on the 
alignment platform being
+        // present (e.g. the protobuf plugin's *ProtoPath configurations do 
not extend it).
         all {
             allVariants {
                 withDependencies {
                     if (removeAll { it.group == "com.sun.activation" && 
it.name == "jakarta.activation" }) {
-                        add("jakarta.activation:jakarta.activation-api")
-                        add("org.eclipse.angus:angus-activation")
+                        
add("jakarta.activation:jakarta.activation-api:$jakartaActivationApiVersion")
+                        
add("org.eclipse.angus:angus-activation:$angusActivationVersion")
                     }
                 }
             }
@@ -122,9 +153,11 @@ dependencies {
         }
     }
 
-    // Enforced platform pins all dependency versions from the version catalog.
-    // This is the Gradle equivalent of Maven's dependencyManagement section.
-    "implementation"(enforcedPlatform(project(":pulsar-dependencies")))
+    // Enforced platform pins all dependency versions from the version catalog 
(the Gradle
+    // equivalent of Maven's dependencyManagement). Declared on the internal, 
non-published
+    // `internalPlatform` bucket (see above) so it aligns the build's 
resolvable classpaths
+    // without leaking the enforced platform into published 
apiElements/runtimeElements metadata.
+    "internalPlatform"(enforcedPlatform(project(":pulsar-dependencies")))
 
     // Resolve lz4-java capability conflict: at.yawk.lz4:lz4-java (used by 
Pulsar) and
     // org.lz4:lz4-java (used by kafka-clients) both provide the 
org.lz4:lz4-java capability.
diff --git 
a/build-logic/conventions/src/main/kotlin/pulsar.nar-conventions.gradle.kts 
b/build-logic/conventions/src/main/kotlin/pulsar.nar-conventions.gradle.kts
index c1014492895..c0c9c1b7eb4 100644
--- a/build-logic/conventions/src/main/kotlin/pulsar.nar-conventions.gradle.kts
+++ b/build-logic/conventions/src/main/kotlin/pulsar.nar-conventions.gradle.kts
@@ -38,7 +38,6 @@ val pulsarPlatformModules = setOf(
     "pulsar-client",
     "pulsar-common",
     "pulsar-config-validation",
-    "bouncy-castle-bc",
     "pulsar-functions-api",
     "pulsar-functions-instance",
     "pulsar-functions-proto",
diff --git 
a/build-logic/conventions/src/main/kotlin/pulsar.public-java-library-conventions.gradle.kts
 
b/build-logic/conventions/src/main/kotlin/pulsar.public-java-library-conventions.gradle.kts
index 45010ea6378..3bb57c41f91 100644
--- 
a/build-logic/conventions/src/main/kotlin/pulsar.public-java-library-conventions.gradle.kts
+++ 
b/build-logic/conventions/src/main/kotlin/pulsar.public-java-library-conventions.gradle.kts
@@ -31,7 +31,7 @@ plugins {
 // Test/compileOnly scoped dependencies are excluded since they don't appear 
in the POM.
 // NAR modules are not validated here — they bundle all dependencies and have 
empty POMs.
 run {
-    val publishedScopes = listOf("api", "implementation", "runtimeOnly")
+    val publishedScopes = listOf("api", "implementation", "runtimeOnly", 
"shadow")
     val configsToCheck = publishedScopes.mapNotNull { name ->
         configurations.findByName(name)?.let { name to it }
     }
diff --git 
a/build-logic/conventions/src/main/kotlin/pulsar.publish-conventions.gradle.kts 
b/build-logic/conventions/src/main/kotlin/pulsar.publish-conventions.gradle.kts
index 5da26aaac64..67436adb31a 100644
--- 
a/build-logic/conventions/src/main/kotlin/pulsar.publish-conventions.gradle.kts
+++ 
b/build-logic/conventions/src/main/kotlin/pulsar.publish-conventions.gradle.kts
@@ -22,6 +22,9 @@
 // the ASF Nexus release/snapshot repositories, and a local deploy repository
 // for testing.
 
+import org.gradle.api.services.BuildService
+import org.gradle.api.services.BuildServiceParameters
+
 plugins {
     `maven-publish`
     signing
@@ -49,26 +52,44 @@ pluginManager.withPlugin("java-library") {
         from(tasks.named(JavaPlugin.JAVADOC_TASK_NAME))
     }
 
-    // Standard java-library modules: publish from components["java"]
-    publishing {
-        publications {
-            create<MavenPublication>("maven") {
-                from(components["java"])
-                artifact(sourcesJar)
-                artifact(javadocJar)
+    // The Maven publication. Its software component is wired in a deferred 
block because it depends
+    // on whether this is a shaded module:
+    //   - Shaded modules (com.gradleup.shadow applied) publish the shadow 
plugin's dependency-reduced
+    //     `shadow` component: the artifact is the shadow jar and the 
published dependencies are
+    //     EXACTLY the `shadow` configuration (the non-bundled runtime deps), 
not the bundled
+    //     component modules + their unshaded transitive tree. This is the 
Gradle equivalent of
+    //     Maven's createDependencyReducedPom.
+    //   - All other java-library modules publish the standard `java` 
component with resolved
+    //     version mapping (unchanged behavior).
+    val mavenPublication = 
publishing.publications.create<MavenPublication>("maven") {
+        artifact(sourcesJar)
+        artifact(javadocJar)
+    }
 
-                versionMapping {
-                    usage(Usage.JAVA_RUNTIME) {
-                        fromResolutionResult()
-                    }
-                    usage(Usage.JAVA_API) {
-                        fromResolutionOf("runtimeClasspath")
-                    }
+    // Shaded modules: the shadow plugin registers components["shadow"] in its 
own afterEvaluate, so
+    // wire it from an afterEvaluate registered AFTER that. Registering inside 
withPlugin (which fires
+    // once the shadow plugin is applied, after this convention) guarantees 
the ordering.
+    pluginManager.withPlugin("com.gradleup.shadow") {
+        afterEvaluate {
+            mavenPublication.from(components["shadow"])
+        }
+    }
+
+    // Non-shaded modules: standard java component + resolved version mapping. 
By afterEvaluate all
+    // plugins are applied, so the shadow-plugin check is reliable.
+    afterEvaluate {
+        if (!pluginManager.hasPlugin("com.gradleup.shadow")) {
+            mavenPublication.from(components["java"])
+            mavenPublication.versionMapping {
+                usage(Usage.JAVA_RUNTIME) {
+                    fromResolutionResult()
+                }
+                usage(Usage.JAVA_API) {
+                    fromResolutionOf("runtimeClasspath")
                 }
             }
         }
     }
-
 }
 
 // --- java-platform projects (BOM, dependencies): POM-only, no JAR ---
@@ -167,10 +188,11 @@ run {
 // Publish with one of:
 //   ./gradlew publishAllPublicationsToApacheSnapshotsRepository   (for 
-SNAPSHOT versions)
 //   ./gradlew publishAllPublicationsToApacheReleasesRepository    (for 
release versions)
-// Releases must be published with --no-parallel: when uploading to the Apache 
staging
-// repository, Nexus creates an implicit staging repository, and concurrent 
per-module uploads
-// can end up split across multiple implicitly-created staging repositories 
instead of being
-// collected into a single one.
+// Uploads to the Apache staging repository are serialized by the 
mavenPublishLock shared build
+// service (defined below) so they all land in a single Nexus staging 
repository: Nexus creates an
+// implicit staging repository on first upload, and concurrent per-module 
uploads could otherwise be
+// split across multiple implicitly-created staging repositories. Because the 
lock handles this,
+// --no-parallel is not required.
 // Credentials are resolved by Gradle at execution time from the 
apacheReleasesUsername /
 // apacheReleasesPassword and apacheSnapshotsUsername / 
apacheSnapshotsPassword Gradle properties
 // (the credentials(PasswordCredentials::class) form, which keeps the publish 
tasks
@@ -180,7 +202,7 @@ run {
 // builds; start the command line with a space to keep the password out of 
shell history:
 //    ORG_GRADLE_PROJECT_apacheReleasesUsername=$APACHE_USER \
 //    ORG_GRADLE_PROJECT_apacheReleasesPassword="<your ASF password>" \
-//    ./gradlew publishAllPublicationsToApacheReleasesRepository --no-parallel 
...
+//    ./gradlew publishAllPublicationsToApacheReleasesRepository ...
 // The URLs can be overridden with the apacheReleasesRepoUrl / 
apacheSnapshotsRepoUrl Gradle
 // properties (e.g. a file:// URL for testing the publication layout).
 run {
@@ -244,6 +266,25 @@ run {
     }
 }
 
+// --- Serialize uploads to Maven repositories ---
+// Nexus creates an implicit staging repository on the first upload of a 
release, and concurrent
+// per-module uploads can end up split across multiple implicitly-created 
staging repositories
+// instead of being collected into a single one. A shared build service with 
maxParallelUsages = 1
+// ensures at most one PublishToMavenRepository upload runs at a time across 
the whole build, so the
+// rest of the build (compilation, jars, signing) can still run in parallel 
and --no-parallel is not
+// required.
+abstract class MavenPublishLock : BuildService<BuildServiceParameters.None>
+
+val mavenPublishLock = gradle.sharedServices.registerIfAbsent(
+    "mavenPublishLock", MavenPublishLock::class
+) {
+    maxParallelUsages = 1
+}
+
+tasks.withType<PublishToMavenRepository>().configureEach {
+    usesService(mavenPublishLock)
+}
+
 // --- GPG signing ---
 signing {
     isRequired = !version.toString().endsWith("-SNAPSHOT")
@@ -265,11 +306,10 @@ tasks.withType<Sign>().configureEach {
         (providers.gradleProperty("useGpgCmd").orNull?.toBoolean() ?: false)
 }
 
-// Suppress enforced-platform validation: all java-library modules use
-// enforcedPlatform(":pulsar-dependencies") for internal version alignment,
-// but this should not leak to consumers. The dependencyManagement section
-// is stripped from published POMs via withXml above.
-tasks.withType<GenerateModuleMetadata>().configureEach {
-    suppressedValidationErrors.add("enforced-platform")
-}
+// NOTE: the enforced-platform validation error is intentionally NOT 
suppressed. The internal
+// version-alignment platform (:pulsar-dependencies) is declared on the 
non-published
+// `internalPlatform` bucket in pulsar.java-conventions (only the resolvable 
build classpaths
+// extend it), so it never reaches the published apiElements/runtimeElements 
variants. Letting the
+// validation run unsuppressed guards against the enforced platform regressing 
back into published
+// Gradle Module Metadata.
 
diff --git a/buildtools/build.gradle.kts b/buildtools/build.gradle.kts
index b33adb06d02..8496a0bef97 100644
--- a/buildtools/build.gradle.kts
+++ b/buildtools/build.gradle.kts
@@ -27,7 +27,7 @@ dependencies {
     implementation(libs.ant)
     implementation(libs.guava)
     implementation(libs.guice)
-    implementation(libs.testng) {
+    api(libs.testng) {
         exclude(group = "org.slf4j")
     }
     implementation(libs.log4j.api)
diff --git a/distribution/server/build.gradle.kts 
b/distribution/server/build.gradle.kts
index ad04b1413b1..7f489628adf 100644
--- a/distribution/server/build.gradle.kts
+++ b/distribution/server/build.gradle.kts
@@ -37,8 +37,11 @@ val distLib by configurations.creating {
     isCanBeResolved = true
     isCanBeConsumed = false
     isTransitive = true
-    // Inherit version constraints from the root project's version catalog
-    extendsFrom(configurations["implementation"])
+    // Inherit the enforced version-alignment platform so bundled dependency 
versions match the
+    // version catalog (the platform lives on the non-published 
`internalPlatform` bucket from
+    // pulsar.java-conventions; `implementation` no longer carries it). Keeps 
the distribution's
+    // resolved versions aligned with what checkBinaryLicense expects.
+    extendsFrom(configurations["implementation"], 
configurations["internalPlatform"])
     // Global exclusions
     exclude(group = "org.projectlombok", module = "lombok")
     // Exclude test frameworks that leak through transitive deps
@@ -142,8 +145,9 @@ dependencies {
     distLib(libs.vertx.core)
     distLib(libs.vertx.web)
 
-    // Bouncy Castle
-    distLib(project(":bouncy-castle:bouncy-castle-bc"))
+    // Bouncy Castle (non-FIPS JCA provider for client-side message crypto + 
TLS)
+    distLib(libs.bcprov.jdk18on)
+    distLib(libs.bcpkix.jdk18on)
 
     // BookKeeper native JARs (these modules publish .nar artifacts by default;
     // we exclude .nar files below and add the .jar variants explicitly)
@@ -263,11 +267,7 @@ val serverDistTar by tasks.registering(Tar::class) {
                     
"${id.group}-${id.module}-${id.version}${classifier}.${ext}"
                 }
                 is 
org.gradle.api.artifacts.component.ProjectComponentIdentifier -> {
-                    var mappedName = file.nameWithoutExtension
-                    // For bouncy-castle-bc, add -pkg classifier
-                    if (mappedName.startsWith("bouncy-castle-bc-")) {
-                        mappedName = mappedName + "-pkg"
-                    }
+                    val mappedName = file.nameWithoutExtension
                     "org.apache.pulsar-${mappedName}.${ext}"
                 }
                 else -> file.name
diff --git a/distribution/shell/build.gradle.kts 
b/distribution/shell/build.gradle.kts
index 6d21bae8d64..9aa6b3d18e2 100644
--- a/distribution/shell/build.gradle.kts
+++ b/distribution/shell/build.gradle.kts
@@ -31,8 +31,10 @@ val distLib by configurations.creating {
     isCanBeResolved = true
     isCanBeConsumed = false
     isTransitive = true
-    // Inherit version constraints from the enforced platform (via 
implementation)
-    extendsFrom(configurations["implementation"])
+    // Inherit the enforced version-alignment platform so bundled dependency 
versions match the
+    // version catalog (the platform lives on the non-published 
`internalPlatform` bucket from
+    // pulsar.java-conventions; `implementation` no longer carries it).
+    extendsFrom(configurations["implementation"], 
configurations["internalPlatform"])
     exclude(group = "org.projectlombok", module = "lombok")
     // Exclude jars not in the shell distribution
     exclude(group = "javax.xml.bind", module = "jaxb-api")
@@ -48,8 +50,9 @@ dependencies {
     distLib(libs.log4j.layout.template.json)
     distLib(libs.log4j.slf4j2.impl)
     distLib(libs.simpleclient.log4j2)
-    // Bouncy Castle
-    distLib(project(":bouncy-castle:bouncy-castle-bc"))
+    // Bouncy Castle (non-FIPS JCA provider for client-side message crypto + 
TLS)
+    distLib(libs.bcprov.jdk18on)
+    distLib(libs.bcpkix.jdk18on)
     // conscrypt (in Maven shell dist)
     distLib(libs.conscrypt.openjdk.uber)
     // swagger-annotations (in Maven shell dist)
@@ -75,10 +78,7 @@ val shellDistTar by tasks.registering(Tar::class) {
             val file = result.file
             val newName = when (id) {
                 is 
org.gradle.api.artifacts.component.ProjectComponentIdentifier -> {
-                    var mappedName = file.nameWithoutExtension
-                    if (mappedName.startsWith("bouncy-castle-bc-")) {
-                        mappedName = mappedName + "-pkg"
-                    }
+                    val mappedName = file.nameWithoutExtension
                     "${mappedName}.${file.extension}"
                 }
                 else -> file.name
@@ -149,10 +149,7 @@ val shellDistZip by tasks.registering(Zip::class) {
             val file = result.file
             val newName = when (id) {
                 is 
org.gradle.api.artifacts.component.ProjectComponentIdentifier -> {
-                    var mappedName = file.nameWithoutExtension
-                    if (mappedName.startsWith("bouncy-castle-bc-")) {
-                        mappedName = mappedName + "-pkg"
-                    }
+                    val mappedName = file.nameWithoutExtension
                     "${mappedName}.${file.extension}"
                 }
                 else -> file.name
diff --git a/managed-ledger/build.gradle.kts b/managed-ledger/build.gradle.kts
index 9782a5a86e2..070dca73b3c 100644
--- a/managed-ledger/build.gradle.kts
+++ b/managed-ledger/build.gradle.kts
@@ -26,13 +26,13 @@ dependencies {
     api(project(":pulsar-common"))
     api(project(":pulsar-metadata"))
     implementation(project(":pulsar-opentelemetry"))
-    implementation(libs.bookkeeper.server)
-    implementation(libs.guava)
+    api(libs.bookkeeper.server)
+    api(libs.guava)
     implementation(libs.roaringbitmap)
     implementation(libs.jctools.core.jdk11)
-    implementation(libs.slog)
-    implementation(libs.simpleclient)
-    implementation(libs.commons.lang3)
+    api(libs.slog)
+    api(libs.simpleclient)
+    api(libs.commons.lang3)
 
     testImplementation(project(":testmocks"))
     testImplementation(libs.dropwizardmetrics.core)
diff --git a/microbench/build.gradle.kts b/microbench/build.gradle.kts
index f9c0a649fd9..9a71b6397b6 100644
--- a/microbench/build.gradle.kts
+++ b/microbench/build.gradle.kts
@@ -23,12 +23,12 @@ plugins {
 }
 
 dependencies {
-    implementation(project(":managed-ledger"))
+    api(project(":managed-ledger"))
     implementation(project(":pulsar-common"))
-    implementation(project(":pulsar-broker"))
+    api(project(":pulsar-broker"))
     implementation(libs.bookkeeper.server)
-    implementation(libs.guava)
-    implementation("org.openjdk.jmh:jmh-core:1.37")
+    api(libs.guava)
+    api("org.openjdk.jmh:jmh-core:1.37")
     annotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:1.37")
 }
 
diff --git a/pulsar-bom/build.gradle.kts b/pulsar-bom/build.gradle.kts
index 060db049449..841c1c258e7 100644
--- a/pulsar-bom/build.gradle.kts
+++ b/pulsar-bom/build.gradle.kts
@@ -92,10 +92,6 @@ dependencies {
         api(project(":pulsar-functions:pulsar-functions-secrets"))
         api(project(":pulsar-functions:pulsar-functions-utils"))
 
-        // Bouncy Castle
-        api(project(":bouncy-castle:bouncy-castle-bc"))
-        api(project(":bouncy-castle:bcfips"))
-
         // Athenz auth
         api(project(":pulsar-client-auth-athenz"))
         api(project(":pulsar-broker-auth-athenz"))
diff --git a/pulsar-broker-auth-athenz/build.gradle.kts 
b/pulsar-broker-auth-athenz/build.gradle.kts
index 1d40a2c6e40..e48fdbbf406 100644
--- a/pulsar-broker-auth-athenz/build.gradle.kts
+++ b/pulsar-broker-auth-athenz/build.gradle.kts
@@ -25,7 +25,7 @@ dependencies {
     implementation(libs.slog)
     compileOnly(project(":pulsar-broker"))
     compileOnly(libs.opentelemetry.api)
-    implementation(project(":pulsar-broker-common"))
+    api(project(":pulsar-broker-common"))
     implementation(project(":pulsar-common"))
     implementation(libs.athenz.zts.java.client)
     implementation(libs.athenz.zpe.java.client)
diff --git a/pulsar-broker-auth-oidc/build.gradle.kts 
b/pulsar-broker-auth-oidc/build.gradle.kts
index a76540c1fb7..8abac178dd2 100644
--- a/pulsar-broker-auth-oidc/build.gradle.kts
+++ b/pulsar-broker-auth-oidc/build.gradle.kts
@@ -24,13 +24,13 @@ plugins {
 
 dependencies {
     implementation(libs.slog)
-    implementation(project(":pulsar-broker-common"))
+    api(project(":pulsar-broker-common"))
     implementation(libs.auth0.java.jwt)
     implementation(libs.auth0.jwks.rsa)
     implementation(libs.caffeine)
     implementation(libs.asynchttpclient)
     implementation(libs.jackson.databind)
-    implementation(libs.jackson.annotations)
+    api(libs.jackson.annotations)
     implementation(libs.kubernetes.client.java) {
         exclude(group = "software.amazon.awssdk")
         // Swagger 1.x annotations on the generated k8s models are inert 
metadata; nothing reads them at runtime
diff --git a/pulsar-broker-auth-sasl/build.gradle.kts 
b/pulsar-broker-auth-sasl/build.gradle.kts
index e5477b5a232..8814e9c303a 100644
--- a/pulsar-broker-auth-sasl/build.gradle.kts
+++ b/pulsar-broker-auth-sasl/build.gradle.kts
@@ -31,13 +31,13 @@ tasks.withType<Test> {
 dependencies {
     implementation(libs.slog)
     implementation(project(":pulsar-broker"))
-    implementation(project(":pulsar-broker-common"))
+    api(project(":pulsar-broker-common"))
     implementation(project(":pulsar-common"))
     implementation(libs.guava)
     implementation(libs.caffeine)
     implementation(libs.commons.lang3)
     implementation(libs.commons.codec)
-    implementation(libs.jakarta.servlet.api)
+    api(libs.jakarta.servlet.api)
     implementation(libs.simpleclient.caffeine)
 
     testImplementation(libs.commons.io)
diff --git a/pulsar-broker-common/build.gradle.kts 
b/pulsar-broker-common/build.gradle.kts
index db0ebf68c4c..868150de359 100644
--- a/pulsar-broker-common/build.gradle.kts
+++ b/pulsar-broker-common/build.gradle.kts
@@ -26,22 +26,27 @@ dependencies {
     api(project(":pulsar-metadata"))
     implementation(libs.slog)
     implementation(libs.guava)
-    implementation(libs.commons.lang3)
-    implementation(libs.netty.common)
-    implementation(libs.bookkeeper.server)
-    implementation(libs.opentelemetry.api)
-    implementation(libs.simpleclient)
+    api(libs.commons.lang3)
+    api(libs.netty.common)
+    api(libs.bookkeeper.server)
+    api(libs.opentelemetry.api)
+    api(libs.simpleclient)
     implementation(libs.caffeine)
-    implementation(libs.jakarta.servlet.api)
-    implementation(libs.jakarta.ws.rs.api)
+    api(libs.jakarta.servlet.api)
+    api(libs.jakarta.ws.rs.api)
     implementation(libs.jjwt.impl)
     implementation(libs.jjwt.jackson)
-    implementation(libs.jetty.server)
+    api(libs.jetty.server)
     implementation(libs.jetty.compression.server)
     implementation(libs.jetty.compression.gzip)
     implementation(libs.jetty.ee10.servlet)
 
-    testImplementation(libs.bc.fips)
+    // Non-FIPS BouncyCastle provider for tests that exercise SecurityUtility 
(which loads
+    // org.bouncycastle.jce.provider.BouncyCastleProvider in a static 
initializer). This matches
+    // the provider used in production. FIPS is covered separately by the 
bcfips-include-test
+    // module; bc-fips must not be on a classpath that also has the non-FIPS 
provider because both
+    // jars define org.bouncycastle.* and the JVM rejects the mismatched 
signers.
+    testImplementation(libs.bcprov.jdk18on)
     testImplementation(libs.awaitility)
     testImplementation(libs.restassured)
     testImplementation(libs.jersey.server)
diff --git a/pulsar-broker/build.gradle.kts b/pulsar-broker/build.gradle.kts
index aeee78be603..6fd3778c0c3 100644
--- a/pulsar-broker/build.gradle.kts
+++ b/pulsar-broker/build.gradle.kts
@@ -26,28 +26,28 @@ plugins {
 }
 
 dependencies {
-    implementation(libs.slog)
+    api(libs.slog)
     api(project(":managed-ledger"))
     api(project(":pulsar-broker-common"))
-    implementation(project(":pulsar-client-original"))
+    api(project(":pulsar-client-original"))
     implementation(project(":pulsar-client-admin-original"))
-    implementation(project(":pulsar-websocket"))
+    api(project(":pulsar-websocket"))
     implementation(project(":pulsar-cli-utils"))
     implementation(project(":pulsar-transaction:pulsar-transaction-common"))
-    
implementation(project(":pulsar-transaction:pulsar-transaction-coordinator"))
-    implementation(project(":pulsar-opentelemetry"))
+    api(project(":pulsar-transaction:pulsar-transaction-coordinator"))
+    api(project(":pulsar-opentelemetry"))
     implementation(project(":pulsar-client-messagecrypto-bc"))
-    implementation(project(":pulsar-functions:pulsar-functions-worker"))
+    api(project(":pulsar-functions:pulsar-functions-worker"))
     implementation(project(":pulsar-docs-tools")) {
         exclude(group = "io.swagger.core.v3")
     }
-    implementation(project(":pulsar-package-management:pulsar-package-core"))
+    api(project(":pulsar-package-management:pulsar-package-core"))
     
implementation(project(":pulsar-package-management:pulsar-package-filesystem-storage"))
 
     implementation(libs.commons.codec)
     implementation(libs.commons.collections4)
-    implementation(libs.commons.lang3)
-    implementation(libs.netty.transport)
+    api(libs.commons.lang3)
+    api(libs.netty.transport)
     implementation(libs.protobuf.java)
     implementation(libs.curator.recipes)
     implementation(libs.bookkeeper.stream.storage.server) {
@@ -60,10 +60,10 @@ dependencies {
     implementation(libs.snappy.java)
     implementation(libs.jetty.server)
     implementation(libs.jetty.alpn.conscrypt.server)
-    implementation(libs.jetty.ee10.servlet)
+    api(libs.jetty.ee10.servlet)
     implementation(libs.jetty.ee10.servlets)
     // ee8 + javax.servlet retained for the legacy AdditionalServlet 
javax.servlet path (PIP-472)
-    implementation(libs.jetty.ee8.servlet)
+    api(libs.jetty.ee8.servlet)
     implementation(libs.javax.servlet.api)
     implementation(libs.jersey.server)
     implementation(libs.jersey.container.servlet.core)
@@ -74,29 +74,29 @@ dependencies {
     implementation(libs.jackson.jakarta.rs.json.provider)
     implementation(libs.jackson.module.jsonSchema)
     implementation(libs.jcl.over.slf4j)
-    implementation(libs.guava)
-    implementation(libs.jspecify)
-    implementation(libs.picocli)
-    implementation(libs.simpleclient)
+    api(libs.guava)
+    api(libs.jspecify)
+    api(libs.picocli)
+    api(libs.simpleclient)
     implementation(libs.simpleclient.hotspot)
     implementation(libs.simpleclient.caffeine)
     implementation(libs.hdrHistogram)
     implementation(libs.gson)
-    implementation(libs.java.semver)
-    implementation(libs.avro)
-    implementation(libs.hppc)
-    implementation(libs.roaringbitmap)
+    api(libs.java.semver)
+    api(libs.avro)
+    api(libs.hppc)
+    api(libs.roaringbitmap)
     implementation(libs.oshi.core)
     implementation(libs.jakarta.xml.bind.api)
     implementation(libs.angus.activation)
-    implementation(libs.bookkeeper.server)
+    api(libs.bookkeeper.server)
     implementation(libs.bookkeeper.circe.checksum)
-    implementation(libs.caffeine)
+    api(libs.caffeine)
     implementation(libs.datasketches.java)
-    implementation(libs.netty.codec.haproxy)
-    implementation(libs.opentelemetry.sdk.extension.autoconfigure)
-    implementation(libs.jetty.ee10.websocket.jetty.server)
-    implementation(libs.jersey.media.multipart)
+    api(libs.netty.codec.haproxy)
+    api(libs.opentelemetry.sdk.extension.autoconfigure)
+    api(libs.jetty.ee10.websocket.jetty.server)
+    api(libs.jersey.media.multipart)
     implementation(libs.bookkeeper.stream.storage.java.client)
     implementation(libs.bookkeeper.stream.storage.service.api)
     implementation(libs.bookkeeper.stream.storage.service.impl)
diff --git a/pulsar-cli-utils/build.gradle.kts 
b/pulsar-cli-utils/build.gradle.kts
index 5e0b01267ef..a12c15610da 100644
--- a/pulsar-cli-utils/build.gradle.kts
+++ b/pulsar-cli-utils/build.gradle.kts
@@ -22,6 +22,6 @@ plugins {
 }
 
 dependencies {
-    implementation(libs.picocli)
+    api(libs.picocli)
     implementation(libs.commons.lang3)
 }
diff --git a/pulsar-client-admin-api/build.gradle.kts 
b/pulsar-client-admin-api/build.gradle.kts
index 48f33b2d074..533bef97f31 100644
--- a/pulsar-client-admin-api/build.gradle.kts
+++ b/pulsar-client-admin-api/build.gradle.kts
@@ -24,5 +24,5 @@ plugins {
 dependencies {
     implementation(libs.slog)
     api(project(":pulsar-client-api"))
-    implementation(libs.jackson.annotations)
+    api(libs.jackson.annotations)
 }
diff --git a/pulsar-client-admin-shaded/build.gradle.kts 
b/pulsar-client-admin-shaded/build.gradle.kts
index df1c0d177f1..8fe0120d3a2 100644
--- a/pulsar-client-admin-shaded/build.gradle.kts
+++ b/pulsar-client-admin-shaded/build.gradle.kts
@@ -23,6 +23,21 @@ plugins {
 }
 
 dependencies {
+    // Bundled into the shaded jar (kept on the runtime classpath so shadowJar 
packs them):
     implementation(project(":pulsar-client-admin-original"))
     implementation(project(":pulsar-client-messagecrypto-bc"))
+
+    // Non-bundled runtime dependencies for the dependency-reduced published 
POM/GMM (the `shadow`
+    // component). Logging is slf4j + slog only — never log4j.
+    "shadow"(project(":pulsar-client-api"))
+    "shadow"(project(":pulsar-client-admin-api"))
+    "shadow"(libs.protobuf.java)
+    "shadow"(libs.jackson.annotations)
+    "shadow"(libs.bcprov.jdk18on)
+    "shadow"(libs.bcpkix.jdk18on)
+    "shadow"(libs.opentelemetry.api)
+    "shadow"(libs.opentelemetry.api.incubator)
+    "shadow"(libs.jspecify)
+    "shadow"(libs.slf4j.api)
+    "shadow"(libs.slog)
 }
diff --git a/pulsar-client-admin/build.gradle.kts 
b/pulsar-client-admin/build.gradle.kts
index 0d95a6c443f..b82bdf95708 100644
--- a/pulsar-client-admin/build.gradle.kts
+++ b/pulsar-client-admin/build.gradle.kts
@@ -24,22 +24,22 @@ plugins {
 dependencies {
     implementation(libs.slog)
     api(project(":pulsar-client-admin-api"))
-    implementation(project(":pulsar-client-original"))
-    implementation(project(":pulsar-common"))
+    api(project(":pulsar-client-original"))
+    api(project(":pulsar-common"))
     implementation(project(":pulsar-package-management:pulsar-package-core"))
-    implementation(libs.jersey.client)
+    api(libs.jersey.client)
     implementation(libs.jersey.media.json.jackson)
     implementation(libs.jersey.media.multipart)
     implementation(libs.jersey.hk2)
     implementation(libs.jackson.jakarta.rs.json.provider)
-    implementation(libs.jackson.databind)
-    implementation(libs.jakarta.ws.rs.api)
+    api(libs.jackson.databind)
+    api(libs.jakarta.ws.rs.api)
     implementation(libs.jakarta.xml.bind.api)
     implementation(libs.jakarta.activation.api)
     runtimeOnly(libs.angus.activation)
     implementation(libs.guava)
-    implementation(libs.gson)
-    implementation(libs.asynchttpclient)
+    api(libs.gson)
+    api(libs.asynchttpclient)
     implementation(libs.commons.lang3)
     implementation(libs.completable.futures)
 
diff --git a/pulsar-client-all/build.gradle.kts 
b/pulsar-client-all/build.gradle.kts
index f6526e423ed..7c49a7d5465 100644
--- a/pulsar-client-all/build.gradle.kts
+++ b/pulsar-client-all/build.gradle.kts
@@ -23,11 +23,26 @@ plugins {
 }
 
 dependencies {
-    implementation(project(":pulsar-client-api"))
+    // Bundled into the shaded jar (kept on the runtime classpath so shadowJar 
packs them):
     implementation(project(":pulsar-client-original"))
     implementation(project(":pulsar-client-admin-original"))
     implementation(project(":pulsar-client-messagecrypto-bc"))
 
+    // Non-bundled runtime dependencies for the dependency-reduced published 
POM/GMM (the `shadow`
+    // component) — union of the client and admin shaded sets. Logging is 
slf4j + slog only — never
+    // log4j (the log4j entries below are test-only and never reach the 
publication).
+    "shadow"(project(":pulsar-client-api"))
+    "shadow"(project(":pulsar-client-admin-api"))
+    "shadow"(libs.protobuf.java)
+    "shadow"(libs.jackson.annotations)
+    "shadow"(libs.bcprov.jdk18on)
+    "shadow"(libs.bcpkix.jdk18on)
+    "shadow"(libs.opentelemetry.api)
+    "shadow"(libs.opentelemetry.api.incubator)
+    "shadow"(libs.jspecify)
+    "shadow"(libs.slf4j.api)
+    "shadow"(libs.slog)
+
     testImplementation(libs.log4j.api)
     testImplementation(libs.log4j.core)
     testImplementation(libs.log4j.slf4j2.impl)
diff --git a/pulsar-client-api-v5/build.gradle.kts 
b/pulsar-client-api-v5/build.gradle.kts
index fa777518491..6b232316368 100644
--- a/pulsar-client-api-v5/build.gradle.kts
+++ b/pulsar-client-api-v5/build.gradle.kts
@@ -18,7 +18,7 @@
  */
 
 plugins {
-    id("pulsar.java-conventions")
+    id("pulsar.public-java-library-conventions")
 }
 
 dependencies {
diff --git a/pulsar-client-auth-athenz/build.gradle.kts 
b/pulsar-client-auth-athenz/build.gradle.kts
index eea4f829ecf..97830fd4451 100644
--- a/pulsar-client-auth-athenz/build.gradle.kts
+++ b/pulsar-client-auth-athenz/build.gradle.kts
@@ -22,7 +22,7 @@ plugins {
 }
 
 dependencies {
-    implementation(project(":pulsar-client-api"))
+    api(project(":pulsar-client-api"))
     implementation(project(":pulsar-client-original"))
     implementation(libs.athenz.zts.java.client)
     implementation(libs.athenz.cert.refresher)
diff --git a/pulsar-client-messagecrypto-bc/build.gradle.kts 
b/pulsar-client-messagecrypto-bc/build.gradle.kts
index 8befb504e5b..553b714703a 100644
--- a/pulsar-client-messagecrypto-bc/build.gradle.kts
+++ b/pulsar-client-messagecrypto-bc/build.gradle.kts
@@ -23,11 +23,13 @@ plugins {
 
 dependencies {
     implementation(libs.slog)
-    compileOnly(project(":pulsar-common"))
-    implementation(project(":pulsar-client-api"))
-    implementation(project(":bouncy-castle:bouncy-castle-bc"))
+    implementation(project(":pulsar-common"))
+    api(project(":pulsar-client-api"))
+    // MessageCryptoBc uses BouncyCastle types directly: bcpkix for PEM 
parsing (PEMParser,
+    // JcaPEMKeyConverter) and bcprov for the EC/IES key specs and ASN.1 types 
used in key handling.
+    // The JCA provider itself is resolved at runtime via SecurityUtility 
(pulsar-common), not here.
     implementation(libs.bcpkix.jdk18on)
-    implementation(libs.bcprov.jdk18on)
+    api(libs.bcprov.jdk18on)
     implementation(libs.guava)
     implementation(libs.caffeine)
     compileOnly(libs.netty.buffer)
diff --git 
a/pulsar-client-messagecrypto-bc/src/main/java/org/apache/pulsar/client/impl/crypto/MessageCryptoBc.java
 
b/pulsar-client-messagecrypto-bc/src/main/java/org/apache/pulsar/client/impl/crypto/MessageCryptoBc.java
index c9e5604a7b7..7b418cd67dc 100644
--- 
a/pulsar-client-messagecrypto-bc/src/main/java/org/apache/pulsar/client/impl/crypto/MessageCryptoBc.java
+++ 
b/pulsar-client-messagecrypto-bc/src/main/java/org/apache/pulsar/client/impl/crypto/MessageCryptoBc.java
@@ -29,8 +29,8 @@ import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
+import java.security.Provider;
 import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.Security;
@@ -64,6 +64,7 @@ import 
org.apache.pulsar.client.api.PulsarClientException.CryptoException;
 import org.apache.pulsar.common.api.proto.EncryptionKeys;
 import org.apache.pulsar.common.api.proto.KeyValue;
 import org.apache.pulsar.common.api.proto.MessageMetadata;
+import org.apache.pulsar.common.util.SecurityUtility;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
@@ -71,7 +72,6 @@ import org.bouncycastle.asn1.x9.ECNamedCurveTable;
 import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.jce.interfaces.ECPublicKey;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.jce.spec.ECParameterSpec;
 import org.bouncycastle.jce.spec.ECPrivateKeySpec;
 import org.bouncycastle.jce.spec.ECPublicKeySpec;
@@ -91,7 +91,22 @@ public class MessageCryptoBc implements 
MessageCrypto<MessageMetadata, MessageMe
     // from assuming hardcoded value. However, it will increase the size of 
the message even further.
     public static final String RSA_TRANS = 
"RSA/NONE/OAEPWithSHA1AndMGF1Padding";
     public static final String AESGCM = "AES/GCM/NoPadding";
-    private static final String AESGCM_PROVIDER_NAME;
+
+    // BouncyCastle JCA provider, resolved lazily on first use via the 
initialization-on-demand holder
+    // idiom. Resolution is delegated to SecurityUtility.getProvider() — the 
same FIPS-agnostic lookup
+    // used elsewhere in Pulsar (e.g. TLS) — so message crypto uses whichever 
BouncyCastle provider is
+    // present on the classpath: the non-FIPS "BC" 
(org.bouncycastle.jce.provider.BouncyCastleProvider)
+    // or the FIPS "BCFIPS" 
(org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider), rather than
+    // hardcoding one. The resolved provider is passed directly to the JCA 
getInstance(...) calls.
+    // Deferring the lookup to first use (asymmetric key wrapping with 
RSA-OAEP/ECIES, or EC key
+    // loading) keeps any resolution failure out of class loading.
+    private static final class BcProviderHolder {
+        static final Provider PROVIDER = SecurityUtility.getProvider();
+    }
+
+    private static Provider bcProvider() {
+        return BcProviderHolder.PROVIDER;
+    }
 
     private static final int tagLen = 16 * 8;
     private final String logCtx;
@@ -117,30 +132,37 @@ public class MessageCryptoBc implements 
MessageCrypto<MessageMetadata, MessageMe
 
         // Initial seed
         secureRandom.nextBytes(new byte[IV_LEN]);
-
-        // Prefer SunJCE provider for AES-GCM for performance reason.
-        // For cases where SunJCE is not available (e.g. non-hotspot JVM), use 
BouncyCastle as fallback.
-        String sunJceProviderName = "SunJCE";
-        if (Security.getProvider(sunJceProviderName) != null) {
-            AESGCM_PROVIDER_NAME = sunJceProviderName;
-        } else {
-            AESGCM_PROVIDER_NAME = BouncyCastleProvider.PROVIDER_NAME;
-        }
-
-        // Add provider only if it's not in the JVM
-        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
-            Security.addProvider(new BouncyCastleProvider());
-        }
     }
 
     // Thread-local instances for non-thread-safe JCA classes
     private static final FastThreadLocal<Cipher> THREAD_LOCAL_CIPHER = new 
FastThreadLocal<Cipher>() {
         @Override
         protected Cipher initialValue() throws Exception {
-            return Cipher.getInstance(AESGCM, AESGCM_PROVIDER_NAME);
+            return createAesGcmCipher();
         }
     };
 
+    /**
+     * Creates an AES-GCM cipher, preferring a JDK-provided implementation for 
performance and broad
+     * JVM compatibility. SunJCE is used when present (HotSpot); otherwise the 
platform default
+     * provider is used (e.g. IBMJCE on IBM JDKs), and BouncyCastle is the 
last resort. The provider
+     * is resolved here — lazily, per thread — rather than during class 
initialization, so a missing
+     * or unusual provider surfaces as a normal crypto exception at use time 
instead of breaking class
+     * loading.
+     */
+    private static Cipher createAesGcmCipher() throws 
NoSuchAlgorithmException, NoSuchPaddingException {
+        Provider sunJce = Security.getProvider("SunJCE");
+        if (sunJce != null) {
+            return Cipher.getInstance(AESGCM, sunJce);
+        }
+        try {
+            // No explicit provider: let the JVM select its default AES-GCM 
implementation.
+            return Cipher.getInstance(AESGCM);
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            return Cipher.getInstance(AESGCM, bcProvider());
+        }
+    }
+
     private static final FastThreadLocal<KeyGenerator> 
THREAD_LOCAL_KEY_GENERATOR =
             new FastThreadLocal<KeyGenerator>() {
         @Override
@@ -215,7 +237,7 @@ public class MessageCryptoBc implements 
MessageCrypto<MessageMetadata, MessageMe
         PublicKey publicKey;
         try (PEMParser pemReader = new PEMParser(keyReader)) {
             Object pemObj = pemReader.readObject();
-            JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter();
+            JcaPEMKeyConverter pemConverter = new 
JcaPEMKeyConverter().setProvider(bcProvider());
             SubjectPublicKeyInfo keyInfo;
             X9ECParameters ecParam = null;
 
@@ -244,11 +266,11 @@ public class MessageCryptoBc implements 
MessageCrypto<MessageMetadata, MessageMe
             if (ecParam != null && ECDSA.equals(publicKey.getAlgorithm())) {
                 ECParameterSpec ecSpec = new 
ECParameterSpec(ecParam.getCurve(), ecParam.getG(), ecParam.getN(),
                         ecParam.getH(), ecParam.getSeed());
-                KeyFactory keyFactory = KeyFactory.getInstance(ECDSA, 
BouncyCastleProvider.PROVIDER_NAME);
+                KeyFactory keyFactory = KeyFactory.getInstance(ECDSA, 
bcProvider());
                 ECPublicKeySpec keySpec = new ECPublicKeySpec(((ECPublicKey) 
publicKey).getQ(), ecSpec);
                 publicKey = keyFactory.generatePublic(keySpec);
             }
-        } catch (IOException | NoSuchAlgorithmException | 
NoSuchProviderException | InvalidKeySpecException e) {
+        } catch (IOException | NoSuchAlgorithmException | 
InvalidKeySpecException e) {
             throw new Exception(e);
         }
         return publicKey;
@@ -279,10 +301,10 @@ public class MessageCryptoBc implements 
MessageCrypto<MessageMetadata, MessageMe
 
             if (pemObj instanceof PEMKeyPair) {
                 PrivateKeyInfo pKeyInfo = ((PEMKeyPair) 
pemObj).getPrivateKeyInfo();
-                JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter();
+                JcaPEMKeyConverter pemConverter = new 
JcaPEMKeyConverter().setProvider(bcProvider());
                 privateKey = pemConverter.getPrivateKey(pKeyInfo);
             } else if (pemObj instanceof PrivateKeyInfo) {
-                JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter();
+                JcaPEMKeyConverter pemConverter = new 
JcaPEMKeyConverter().setProvider(bcProvider());
                 privateKey = pemConverter.getPrivateKey((PrivateKeyInfo) 
pemObj);
             }
 
@@ -291,7 +313,7 @@ public class MessageCryptoBc implements 
MessageCrypto<MessageMetadata, MessageMe
             if (ecParam != null && ECDSA.equals(privateKey.getAlgorithm())) {
                 ECParameterSpec ecSpec = new 
ECParameterSpec(ecParam.getCurve(), ecParam.getG(), ecParam.getN(),
                         ecParam.getH(), ecParam.getSeed());
-                KeyFactory keyFactory = KeyFactory.getInstance(ECDSA, 
BouncyCastleProvider.PROVIDER_NAME);
+                KeyFactory keyFactory = KeyFactory.getInstance(ECDSA, 
bcProvider());
                 ECPrivateKeySpec keySpec = new 
ECPrivateKeySpec(((ECPrivateKey) privateKey).getS(), ecSpec);
                 privateKey = keyFactory.generatePrivate(keySpec);
             }
@@ -346,9 +368,9 @@ public class MessageCryptoBc implements 
MessageCrypto<MessageMetadata, MessageMe
             AlgorithmParameterSpec params = null;
             // Encrypt data key using public key
             if (RSA.equals(pubKey.getAlgorithm())) {
-                dataKeyCipher = Cipher.getInstance(RSA_TRANS, 
BouncyCastleProvider.PROVIDER_NAME);
+                dataKeyCipher = Cipher.getInstance(RSA_TRANS, bcProvider());
             } else if (ECDSA.equals(pubKey.getAlgorithm())) {
-                dataKeyCipher = Cipher.getInstance(ECIES, 
BouncyCastleProvider.PROVIDER_NAME);
+                dataKeyCipher = Cipher.getInstance(ECIES, bcProvider());
                 params = createIESParameterSpec();
             } else {
                 String msg = logCtx + "Unsupported key type " + 
pubKey.getAlgorithm() + " for key " + keyName;
@@ -361,7 +383,7 @@ public class MessageCryptoBc implements 
MessageCrypto<MessageMetadata, MessageMe
                 dataKeyCipher.init(Cipher.ENCRYPT_MODE, pubKey);
             }
             encryptedKey = dataKeyCipher.doFinal(encryptionKey.getEncoded());
-        } catch (IllegalBlockSizeException | BadPaddingException | 
NoSuchAlgorithmException | NoSuchProviderException
+        } catch (IllegalBlockSizeException | BadPaddingException | 
NoSuchAlgorithmException
                  | NoSuchPaddingException | InvalidKeyException | 
InvalidAlgorithmParameterException e) {
             log.error().attr("logCtx", logCtx).attr("keyName", keyName)
                     .exceptionMessage(e).log("Failed to encrypt data key");
@@ -508,9 +530,9 @@ public class MessageCryptoBc implements 
MessageCrypto<MessageMetadata, MessageMe
             Cipher dataKeyCipher;
             // Decrypt data key using private key
             if (RSA.equals(privateKey.getAlgorithm())) {
-                dataKeyCipher = Cipher.getInstance(RSA_TRANS, 
BouncyCastleProvider.PROVIDER_NAME);
+                dataKeyCipher = Cipher.getInstance(RSA_TRANS, bcProvider());
             } else if (ECDSA.equals(privateKey.getAlgorithm())) {
-                dataKeyCipher = Cipher.getInstance(ECIES, 
BouncyCastleProvider.PROVIDER_NAME);
+                dataKeyCipher = Cipher.getInstance(ECIES, bcProvider());
                 params = createIESParameterSpec();
             } else {
                 log.error().attr("keyType", 
privateKey.getAlgorithm()).attr("keyName", keyName)
diff --git a/pulsar-client-shaded/build.gradle.kts 
b/pulsar-client-shaded/build.gradle.kts
index 4822c850d10..654e50f7273 100644
--- a/pulsar-client-shaded/build.gradle.kts
+++ b/pulsar-client-shaded/build.gradle.kts
@@ -23,7 +23,24 @@ plugins {
 }
 
 dependencies {
-    implementation(project(":pulsar-client-api"))
+    // Bundled into the shaded jar (kept on the runtime classpath so shadowJar 
packs them):
     implementation(project(":pulsar-client-original"))
     implementation(project(":pulsar-client-messagecrypto-bc"))
+
+    // Non-bundled runtime dependencies. These are the ONLY entries in the 
dependency-reduced
+    // published POM/Gradle Module Metadata (the `shadow` component). The 
bundled modules above and
+    // their relocated third-party deps live inside the jar and must NOT 
appear as POM dependencies.
+    // pulsar-client-api, protobuf, jackson-annotations, the BouncyCastle jars 
(signed, not
+    // relocatable), OpenTelemetry and jspecify are deliberately not bundled. 
Logging is slf4j + slog
+    // only — never log4j.
+    "shadow"(project(":pulsar-client-api"))
+    "shadow"(libs.protobuf.java)
+    "shadow"(libs.jackson.annotations)
+    "shadow"(libs.bcprov.jdk18on)
+    "shadow"(libs.bcpkix.jdk18on)
+    "shadow"(libs.opentelemetry.api)
+    "shadow"(libs.opentelemetry.api.incubator)
+    "shadow"(libs.jspecify)
+    "shadow"(libs.slf4j.api)
+    "shadow"(libs.slog)
 }
diff --git a/pulsar-client-tools/build.gradle.kts 
b/pulsar-client-tools/build.gradle.kts
index 6db5482e942..e97417c862d 100644
--- a/pulsar-client-tools/build.gradle.kts
+++ b/pulsar-client-tools/build.gradle.kts
@@ -24,10 +24,10 @@ plugins {
 dependencies {
     implementation(libs.slog)
     api(project(":pulsar-client-tools-api"))
-    implementation(project(":pulsar-client-admin-api"))
+    api(project(":pulsar-client-admin-api"))
     implementation(project(":pulsar-client-admin-original"))
     implementation(project(":pulsar-client-original"))
-    implementation(project(":pulsar-client-api-v5"))
+    api(project(":pulsar-client-api-v5"))
     implementation(project(":pulsar-client-v5"))
     implementation(project(":pulsar-common"))
     implementation(project(":pulsar-client-messagecrypto-bc"))
@@ -35,9 +35,9 @@ dependencies {
     implementation(project(":pulsar-websocket")) {
         exclude(group = "*", module = "*")
     }
-    implementation(libs.picocli)
+    api(libs.picocli)
     implementation(libs.picocli.shell.jline3)
-    implementation(libs.jline)
+    api(libs.jline)
     implementation(libs.commons.io)
     implementation(libs.commons.lang3)
     // guava was previously leaked onto the compile classpath via 
compileOnly(swagger-core 1.x)
@@ -45,11 +45,11 @@ dependencies {
     implementation(libs.commons.text)
     implementation(libs.asynchttpclient)
     implementation(libs.netty.reactive.streams)
-    implementation(libs.gson)
+    api(libs.gson)
     implementation(libs.javassist)
     implementation(libs.avro)
     implementation(libs.jetty.client)
-    implementation(libs.jetty.websocket.jetty.api)
+    api(libs.jetty.websocket.jetty.api)
     implementation(libs.jetty.websocket.jetty.client)
     runtimeOnly(libs.jna)
 
diff --git a/pulsar-client-v5/build.gradle.kts 
b/pulsar-client-v5/build.gradle.kts
index 0a5ebda446d..8e564ca4593 100644
--- a/pulsar-client-v5/build.gradle.kts
+++ b/pulsar-client-v5/build.gradle.kts
@@ -18,11 +18,11 @@
  */
 
 plugins {
-    id("pulsar.java-conventions")
+    id("pulsar.public-java-library-conventions")
 }
 
 dependencies {
-    implementation(project(":pulsar-client-api-v5"))
+    api(project(":pulsar-client-api-v5"))
     implementation(project(":pulsar-client-original"))
     implementation(project(":pulsar-common"))
     implementation(libs.slf4j.api)
diff --git a/pulsar-client/build.gradle.kts b/pulsar-client/build.gradle.kts
index 43c382b2478..c6c623a9b82 100644
--- a/pulsar-client/build.gradle.kts
+++ b/pulsar-client/build.gradle.kts
@@ -24,13 +24,15 @@ plugins {
 
 dependencies {
     api(project(":pulsar-client-api"))
-    implementation(project(":pulsar-common")) {
+    api(project(":pulsar-common")) {
         exclude(group = "io.prometheus", module = "simpleclient_caffeine")
     }
-    implementation(project(":bouncy-castle:bouncy-castle-bc"))
+    // Non-FIPS BouncyCastle JCA provider, needed at runtime by 
pulsar-client-messagecrypto-bc.
+    implementation(libs.bcprov.jdk18on)
+    implementation(libs.bcpkix.jdk18on)
     compileOnly(project(":pulsar-client-messagecrypto-bc"))
 
-    implementation(libs.opentelemetry.api)
+    api(libs.opentelemetry.api)
     implementation(libs.opentelemetry.api.incubator)
     implementation(libs.netty.codec.http)
     implementation(libs.netty.handler.proxy)
@@ -38,18 +40,18 @@ dependencies {
     implementation(libs.netty.resolver.dns)
     implementation(variantOf(libs.netty.resolver.dns.native.macos) { 
classifier("osx-aarch_64") })
     implementation(variantOf(libs.netty.resolver.dns.native.macos) { 
classifier("osx-x86_64") })
-    implementation(libs.guava)
+    api(libs.guava)
     implementation(libs.bookkeeper.circe.checksum) {
         exclude(group = "io.netty")
     }
     implementation(libs.commons.lang3)
-    implementation(libs.asynchttpclient)
+    api(libs.asynchttpclient)
     implementation(libs.netty.reactive.streams)
-    implementation(libs.slog)
+    api(libs.slog)
     implementation(libs.commons.codec)
     implementation(libs.datasketches.java)
     implementation(libs.gson)
-    implementation(libs.avro) {
+    api(libs.avro) {
         exclude(group = "org.slf4j")
     }
     implementation(libs.avro.protobuf) {
@@ -57,7 +59,7 @@ dependencies {
     }
     implementation(libs.jackson.module.jsonSchema)
     implementation(libs.jsr305)
-    implementation(libs.jspecify)
+    api(libs.jspecify)
     implementation(libs.roaringbitmap)
 
     compileOnly(libs.swagger.annotations)
diff --git a/pulsar-common/build.gradle.kts b/pulsar-common/build.gradle.kts
index 6e192df5a64..e3b9595ab1f 100644
--- a/pulsar-common/build.gradle.kts
+++ b/pulsar-common/build.gradle.kts
@@ -148,17 +148,17 @@ dependencies {
     api(project(":pulsar-client-api"))
     api(project(":pulsar-client-admin-api"))
 
-    implementation(libs.jackson.databind)
+    api(libs.jackson.databind)
     implementation(libs.jackson.module.parameter.names)
     implementation(libs.jackson.datatype.jsr310)
     implementation(libs.jackson.datatype.jdk8)
     implementation(libs.jackson.dataformat.yaml)
-    implementation(libs.guava)
-    implementation(libs.simpleclient.caffeine)
-    implementation(libs.jspecify)
-    implementation(libs.netty.handler)
-    implementation(libs.netty.buffer)
-    implementation(libs.netty.resolver.dns)
+    api(libs.guava)
+    api(libs.simpleclient.caffeine)
+    api(libs.jspecify)
+    api(libs.netty.handler)
+    api(libs.netty.buffer)
+    api(libs.netty.resolver.dns)
     implementation(variantOf(libs.netty.transport.native.epoll) { 
classifier("linux-x86_64") })
     implementation(variantOf(libs.netty.transport.native.epoll) { 
classifier("linux-aarch_64") })
     implementation(libs.netty.transport.native.unix.common)
@@ -170,7 +170,7 @@ dependencies {
         exclude(group = "commons-configuration", module = 
"commons-configuration2")
         exclude(group = "commons-beanutils", module = "commons-beanutils")
     }
-    implementation(libs.aircompressor)
+    api(libs.aircompressor)
     implementation(libs.bookkeeper.circe.checksum) {
         exclude(group = "io.netty")
         exclude(group = "commons-configuration", module = 
"commons-configuration2")
@@ -184,18 +184,23 @@ dependencies {
     implementation(libs.netty.transport.classes.io.uring)
     implementation(variantOf(libs.netty.transport.native.io.uring) { 
classifier("linux-x86_64") })
     implementation(variantOf(libs.netty.transport.native.io.uring) { 
classifier("linux-aarch_64") })
-    implementation(libs.netty.codec.haproxy)
+    api(libs.netty.codec.haproxy)
     implementation(libs.commons.lang3)
-    implementation(libs.jakarta.ws.rs.api)
+    api(libs.jakarta.ws.rs.api)
     implementation(libs.commons.io)
     implementation(libs.re2j)
     implementation(libs.completable.futures)
-    implementation(libs.gson)
+    api(libs.gson)
 
     compileOnly(libs.swagger.annotations)
     compileOnly(libs.spotbugs.annotations)
 
-    testImplementation(libs.bc.fips)
+    // Non-FIPS BouncyCastle provider for tests that exercise SecurityUtility 
(which loads
+    // org.bouncycastle.jce.provider.BouncyCastleProvider in a static 
initializer). This matches
+    // the provider used in production. FIPS is covered separately by the 
bcfips-include-test
+    // module; bc-fips must not be on a classpath that also has the non-FIPS 
provider because both
+    // jars define org.bouncycastle.* and the JVM rejects the mismatched 
signers.
+    testImplementation(libs.bcprov.jdk18on)
     testImplementation(libs.lz4.java)
     testImplementation(libs.zstd.jni)
     testImplementation(libs.snappy.java)
diff --git 
a/pulsar-common/src/main/java/org/apache/pulsar/common/util/BCLoader.java 
b/pulsar-common/src/main/java/org/apache/pulsar/common/util/BCLoader.java
deleted file mode 100644
index 268cd430e68..00000000000
--- a/pulsar-common/src/main/java/org/apache/pulsar/common/util/BCLoader.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.pulsar.common.util;
-
-import java.security.Provider;
-
-/**
- * This interface declares a Bouncy Castle provider Loader.
- */
-public interface BCLoader {
-    Provider getProvider();
-}
diff --git a/pulsar-docs-tools/build.gradle.kts 
b/pulsar-docs-tools/build.gradle.kts
index 4438ae22edd..a30427b9221 100644
--- a/pulsar-docs-tools/build.gradle.kts
+++ b/pulsar-docs-tools/build.gradle.kts
@@ -22,9 +22,9 @@ plugins {
 }
 
 dependencies {
-    implementation(libs.commons.lang3)
+    api(libs.commons.lang3)
     implementation(libs.guava)
     implementation(libs.slog)
     implementation(libs.swagger.annotations)
-    implementation(libs.picocli)
+    api(libs.picocli)
 }
diff --git a/pulsar-functions/instance/build.gradle.kts 
b/pulsar-functions/instance/build.gradle.kts
index f9e9494fe52..1482d258321 100644
--- a/pulsar-functions/instance/build.gradle.kts
+++ b/pulsar-functions/instance/build.gradle.kts
@@ -24,30 +24,30 @@ plugins {
 dependencies {
     implementation(libs.slog)
     api(project(":pulsar-functions:pulsar-functions-utils"))
-    implementation(project(":pulsar-functions:pulsar-functions-api"))
-    implementation(project(":pulsar-functions:pulsar-functions-secrets"))
-    implementation(project(":pulsar-functions:pulsar-functions-proto"))
+    api(project(":pulsar-functions:pulsar-functions-api"))
+    api(project(":pulsar-functions:pulsar-functions-secrets"))
+    api(project(":pulsar-functions:pulsar-functions-proto"))
     implementation(project(":pulsar-metadata"))
-    implementation(project(":pulsar-io:pulsar-io-core"))
-    implementation(project(":pulsar-client-original"))
+    api(project(":pulsar-io:pulsar-io-core"))
+    api(project(":pulsar-client-original"))
     implementation(project(":pulsar-client-admin-original"))
     implementation(project(":pulsar-client-messagecrypto-bc"))
-    implementation(libs.guava)
+    api(libs.guava)
     implementation(libs.gson)
-    implementation(libs.commons.lang3)
+    api(libs.commons.lang3)
     implementation(libs.caffeine)
     implementation(libs.picocli)
     implementation(libs.typetools)
-    implementation(libs.simpleclient)
+    api(libs.simpleclient)
     implementation(libs.simpleclient.hotspot)
     implementation(libs.simpleclient.caffeine)
     implementation(libs.simpleclient.httpserver)
     implementation(libs.prometheus.jmx.collector)
     implementation(libs.datasketches.java)
     implementation(libs.jackson.databind)
-    implementation(libs.netty.buffer)
+    api(libs.netty.buffer)
     implementation(libs.netty.common)
-    implementation(libs.bookkeeper.stream.storage.java.client) {
+    api(libs.bookkeeper.stream.storage.java.client) {
         exclude(group = "io.grpc")
         exclude(group = "com.google.protobuf")
     }
@@ -60,7 +60,7 @@ dependencies {
     runtimeOnly(libs.perfmark.api)
     implementation(libs.log4j.slf4j2.impl)
     implementation(libs.log4j.api)
-    implementation(libs.log4j.core)
+    api(libs.log4j.core)
     implementation(libs.bcpkix.jdk18on)
     implementation(libs.bookkeeper.circe.checksum)
     // Main code only touches com.google.protobuf reflectively (protobuf 
schema detection for user
diff --git 
a/pulsar-functions/instance/src/main/java/org/apache/pulsar/functions/instance/ProducerBuilderFactory.java
 
b/pulsar-functions/instance/src/main/java/org/apache/pulsar/functions/instance/ProducerBuilderFactory.java
index a73c9d6d1a6..1ed3a9962e9 100644
--- 
a/pulsar-functions/instance/src/main/java/org/apache/pulsar/functions/instance/ProducerBuilderFactory.java
+++ 
b/pulsar-functions/instance/src/main/java/org/apache/pulsar/functions/instance/ProducerBuilderFactory.java
@@ -20,7 +20,6 @@ package org.apache.pulsar.functions.instance;
 
 import static org.apache.commons.lang3.StringUtils.isEmpty;
 import com.google.common.annotations.VisibleForTesting;
-import java.security.Security;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import lombok.Builder;
@@ -38,7 +37,6 @@ import org.apache.pulsar.client.api.Schema;
 import org.apache.pulsar.common.functions.CryptoConfig;
 import org.apache.pulsar.common.functions.ProducerConfig;
 import org.apache.pulsar.functions.utils.CryptoUtils;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 /**
  * This class is responsible for creating ProducerBuilders with the 
appropriate configurations to
@@ -162,11 +160,6 @@ public class ProducerBuilderFactory {
 
         CryptoConfig cryptoConfig = producerConfig.getCryptoConfig();
 
-        // add provider only if it's not in the JVM
-        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
-            Security.addProvider(new BouncyCastleProvider());
-        }
-
         final String[] encryptionKeys = cryptoConfig.getEncryptionKeys();
         Crypto.CryptoBuilder bldr = Crypto.builder()
                 .failureAction(cryptoConfig.getProducerCryptoFailureAction())
diff --git 
a/pulsar-functions/instance/src/main/java/org/apache/pulsar/functions/source/PulsarSource.java
 
b/pulsar-functions/instance/src/main/java/org/apache/pulsar/functions/source/PulsarSource.java
index 835659d4a7d..9cdb3405926 100644
--- 
a/pulsar-functions/instance/src/main/java/org/apache/pulsar/functions/source/PulsarSource.java
+++ 
b/pulsar-functions/instance/src/main/java/org/apache/pulsar/functions/source/PulsarSource.java
@@ -18,7 +18,6 @@
  */
 package org.apache.pulsar.functions.source;
 
-import java.security.Security;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -39,7 +38,6 @@ import org.apache.pulsar.functions.api.Record;
 import org.apache.pulsar.functions.utils.CryptoUtils;
 import org.apache.pulsar.functions.utils.MessagePayloadProcessorUtils;
 import org.apache.pulsar.io.core.Source;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 public abstract class PulsarSource<T> implements Source<T> {
     protected final PulsarClient pulsarClient;
@@ -185,11 +183,6 @@ public abstract class PulsarSource<T> implements Source<T> 
{
         consumerConfBuilder.schema(schema);
 
         if (conf.getCryptoConfig() != null) {
-            // add provider only if it's not in the JVM
-            if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == 
null) {
-                Security.addProvider(new BouncyCastleProvider());
-            }
-
             
consumerConfBuilder.consumerCryptoFailureAction(conf.getCryptoConfig().getConsumerCryptoFailureAction());
             
consumerConfBuilder.cryptoKeyReader(CryptoUtils.getCryptoKeyReaderInstance(
                     conf.getCryptoConfig().getCryptoKeyReaderClassName(),
diff --git a/pulsar-functions/localrun/build.gradle.kts 
b/pulsar-functions/localrun/build.gradle.kts
index 4ab324064f9..ed7a3285728 100644
--- a/pulsar-functions/localrun/build.gradle.kts
+++ b/pulsar-functions/localrun/build.gradle.kts
@@ -25,7 +25,7 @@ dependencies {
     implementation(libs.slog)
     api(project(":pulsar-functions:pulsar-functions-instance"))
     api(project(":pulsar-functions:pulsar-functions-runtime"))
-    implementation(libs.picocli)
+    api(libs.picocli)
     implementation(libs.gson)
     implementation(libs.commons.lang3)
     implementation(libs.simpleclient.httpserver)
diff --git a/pulsar-functions/runtime/build.gradle.kts 
b/pulsar-functions/runtime/build.gradle.kts
index 7750e88989e..507c4b54e37 100644
--- a/pulsar-functions/runtime/build.gradle.kts
+++ b/pulsar-functions/runtime/build.gradle.kts
@@ -38,12 +38,12 @@ tasks.named<ProcessResources>("processTestResources") {
 dependencies {
     implementation(libs.slog)
     api(project(":pulsar-functions:pulsar-functions-instance"))
-    implementation(project(":pulsar-functions:pulsar-functions-secrets"))
-    implementation(project(":pulsar-broker-common"))
-    implementation(libs.picocli)
+    api(project(":pulsar-functions:pulsar-functions-secrets"))
+    api(project(":pulsar-broker-common"))
+    api(libs.picocli)
     implementation(libs.jackson.dataformat.yaml)
     implementation(libs.jackson.databind)
-    implementation(libs.jackson.annotations)
+    api(libs.jackson.annotations)
     implementation(libs.commons.lang3)
     implementation(libs.kubernetes.client.java) {
         exclude(group = "org.bouncycastle", module = "bcpkix-jdk18on")
@@ -58,7 +58,7 @@ dependencies {
     implementation(libs.prometheus.jmx.collector)
     implementation(libs.protobuf.java.util)
     implementation(libs.guava)
-    implementation(project(":pulsar-functions:pulsar-functions-proto"))
+    api(project(":pulsar-functions:pulsar-functions-proto"))
     implementation(project(":pulsar-client-original"))
     implementation(libs.grpc.netty.shaded)
     implementation(libs.grpc.stub)
diff --git a/pulsar-functions/secrets/build.gradle.kts 
b/pulsar-functions/secrets/build.gradle.kts
index 0dd441a0f24..b08de752c37 100644
--- a/pulsar-functions/secrets/build.gradle.kts
+++ b/pulsar-functions/secrets/build.gradle.kts
@@ -22,7 +22,7 @@ plugins {
 }
 
 dependencies {
-    implementation(project(":pulsar-functions:pulsar-functions-proto"))
+    api(project(":pulsar-functions:pulsar-functions-proto"))
     implementation(libs.kubernetes.client.java) {
         exclude(group = "software.amazon.awssdk")
         // Swagger 1.x annotations on the generated k8s models are inert 
metadata; nothing reads them at runtime
diff --git a/pulsar-functions/utils/build.gradle.kts 
b/pulsar-functions/utils/build.gradle.kts
index 13a8a910f3e..9e0d4fa1a6a 100644
--- a/pulsar-functions/utils/build.gradle.kts
+++ b/pulsar-functions/utils/build.gradle.kts
@@ -25,7 +25,7 @@ dependencies {
     implementation(libs.slog)
     api(project(":pulsar-common"))
     api(project(":pulsar-functions:pulsar-functions-api"))
-    implementation(project(":pulsar-functions:pulsar-functions-proto"))
+    api(project(":pulsar-functions:pulsar-functions-proto"))
     implementation(project(":pulsar-io:pulsar-io-core"))
     implementation(project(":pulsar-config-validation"))
     implementation(libs.commons.lang3)
@@ -34,7 +34,7 @@ dependencies {
     implementation(project(":pulsar-client-original"))
     implementation(libs.protobuf.java)
     implementation(libs.protobuf.java.util)
-    implementation(libs.byte.buddy)
+    api(libs.byte.buddy)
     implementation(libs.zt.zip)
     implementation(libs.guava)
 
diff --git a/pulsar-functions/worker/build.gradle.kts 
b/pulsar-functions/worker/build.gradle.kts
index a4201d6a2ae..5c7890e2194 100644
--- a/pulsar-functions/worker/build.gradle.kts
+++ b/pulsar-functions/worker/build.gradle.kts
@@ -38,11 +38,11 @@ tasks.named<ProcessResources>("processTestResources") {
 dependencies {
     implementation(libs.slog)
     api(project(":pulsar-functions:pulsar-functions-runtime"))
-    implementation(project(":pulsar-broker-common"))
+    api(project(":pulsar-broker-common"))
     implementation(project(":pulsar-opentelemetry"))
     implementation(project(":pulsar-client-original"))
     implementation(project(":pulsar-client-admin-original"))
-    implementation(project(":pulsar-functions:pulsar-functions-proto"))
+    api(project(":pulsar-functions:pulsar-functions-proto"))
     implementation(project(":pulsar-functions:pulsar-functions-secrets"))
     implementation(project(":pulsar-docs-tools")) {
         exclude(group = "io.swagger.core.v3")
@@ -54,12 +54,12 @@ dependencies {
     implementation(libs.guava)
     implementation(libs.gson)
     implementation(libs.picocli)
-    implementation(libs.jackson.core)
-    implementation(libs.jackson.databind)
+    api(libs.jackson.core)
+    api(libs.jackson.databind)
     implementation(libs.netty.common)
     implementation(libs.byte.buddy)
 
-    implementation(libs.distributedlog.core) {
+    api(libs.distributedlog.core) {
         exclude(group = "net.jpountz.lz4", module = "lz4")
     }
     implementation(libs.bookkeeper.server)
@@ -69,7 +69,7 @@ dependencies {
     implementation(libs.jersey.container.servlet)
     implementation(libs.jersey.container.servlet.core)
     implementation(libs.jersey.media.json.jackson)
-    implementation(libs.jersey.media.multipart)
+    api(libs.jersey.media.multipart)
 
     implementation(libs.jetty.server)
     implementation(libs.jetty.alpn.conscrypt.server)
@@ -77,7 +77,7 @@ dependencies {
     implementation(libs.jetty.ee10.servlets)
 
     implementation(libs.jakarta.activation.api)
-    implementation(libs.jakarta.ws.rs.api)
+    api(libs.jakarta.ws.rs.api)
 
     implementation(libs.simpleclient)
     implementation(libs.simpleclient.hotspot)
diff --git a/pulsar-io/batch-data-generator/build.gradle.kts 
b/pulsar-io/batch-data-generator/build.gradle.kts
index c9505b6a090..97dafcf249e 100644
--- a/pulsar-io/batch-data-generator/build.gradle.kts
+++ b/pulsar-io/batch-data-generator/build.gradle.kts
@@ -23,10 +23,10 @@ plugins {
 }
 dependencies {
     implementation(libs.slog)
-    implementation(project(":pulsar-io:pulsar-io-core"))
+    api(project(":pulsar-io:pulsar-io-core"))
     implementation(project(":pulsar-io:pulsar-io-batch-discovery-triggerers"))
     implementation(libs.spring.context)
-    implementation(libs.jfairy)
+    api(libs.jfairy)
     implementation(libs.avro)
 
     
testImplementation(project(":pulsar-functions:pulsar-functions-local-runner-original"))
diff --git a/pulsar-io/data-generator/build.gradle.kts 
b/pulsar-io/data-generator/build.gradle.kts
index bbdc495048c..0db651943f4 100644
--- a/pulsar-io/data-generator/build.gradle.kts
+++ b/pulsar-io/data-generator/build.gradle.kts
@@ -23,8 +23,8 @@ plugins {
 }
 dependencies {
     implementation(libs.slog)
-    implementation(project(":pulsar-io:pulsar-io-core"))
+    api(project(":pulsar-io:pulsar-io-core"))
     implementation(project(":pulsar-config-validation"))
-    implementation(libs.jfairy)
+    api(libs.jfairy)
     implementation(libs.avro)
 }
diff --git a/pulsar-metadata/build.gradle.kts b/pulsar-metadata/build.gradle.kts
index 4a27ab0d9fc..c0469923ac3 100644
--- a/pulsar-metadata/build.gradle.kts
+++ b/pulsar-metadata/build.gradle.kts
@@ -23,13 +23,13 @@ plugins {
 
 dependencies {
     api(project(":pulsar-common"))
-    implementation(libs.bookkeeper.server)
+    api(libs.bookkeeper.server)
     implementation(libs.zookeeper) {
         exclude(group = "org.slf4j")
     }
     implementation(libs.jackson.dataformat.yaml)
     implementation(libs.oxia.client)
-    implementation(libs.slog)
+    api(libs.slog)
     implementation(libs.caffeine)
     implementation(libs.simpleclient)
     implementation(libs.simpleclient.caffeine)
diff --git a/pulsar-opentelemetry/build.gradle.kts 
b/pulsar-opentelemetry/build.gradle.kts
index 333529da4a2..32718c76153 100644
--- a/pulsar-opentelemetry/build.gradle.kts
+++ b/pulsar-opentelemetry/build.gradle.kts
@@ -26,7 +26,7 @@ dependencies {
     implementation(libs.opentelemetry.exporter.otlp)
     implementation(libs.opentelemetry.exporter.prometheus)
     implementation(libs.opentelemetry.sdk)
-    implementation(libs.opentelemetry.sdk.extension.autoconfigure)
+    api(libs.opentelemetry.sdk.extension.autoconfigure)
     implementation(libs.opentelemetry.instrumentation.resources)
     implementation(libs.opentelemetry.semconv)
     implementation(libs.opentelemetry.instrumentation.runtime.telemetry)
diff --git a/pulsar-proxy/build.gradle.kts b/pulsar-proxy/build.gradle.kts
index 12724d2ae79..d57bd0a885f 100644
--- a/pulsar-proxy/build.gradle.kts
+++ b/pulsar-proxy/build.gradle.kts
@@ -25,21 +25,21 @@ plugins {
 dependencies {
     implementation(libs.slog)
     api(project(":pulsar-broker-common"))
-    implementation(project(":pulsar-client-original"))
-    implementation(project(":pulsar-common"))
+    api(project(":pulsar-client-original"))
+    api(project(":pulsar-common"))
     implementation(project(":pulsar-opentelemetry"))
-    implementation(project(":pulsar-docs-tools"))
-    implementation(project(":pulsar-websocket"))
+    api(project(":pulsar-docs-tools"))
+    api(project(":pulsar-websocket"))
     implementation(libs.commons.lang3)
     implementation(libs.jetty.server)
     implementation(libs.jetty.alpn.conscrypt.server)
     implementation(libs.jetty.client)
-    implementation(libs.jetty.ee10.servlet)
+    api(libs.jetty.ee10.servlet)
     implementation(libs.jetty.ee10.servlets)
     implementation(libs.jetty.ee10.proxy)
     implementation(libs.jetty.ee10.websocket.jetty.server)
     // ee8 + javax.servlet retained for the legacy AdditionalServlet 
javax.servlet path (PIP-472)
-    implementation(libs.jetty.ee8.servlet)
+    api(libs.jetty.ee8.servlet)
     implementation(libs.javax.servlet.api)
     implementation(libs.jersey.server)
     implementation(libs.jersey.container.servlet.core)
@@ -51,20 +51,20 @@ dependencies {
     implementation(libs.simpleclient.hotspot)
     implementation(libs.simpleclient.servlet)
     implementation(libs.jackson.jakarta.rs.json.provider)
-    implementation(libs.picocli)
+    api(libs.picocli)
     implementation(libs.log4j.core)
     implementation(libs.log4j.api)
     implementation(libs.ipaddress)
     implementation(libs.netty.handler)
-    implementation(libs.netty.transport)
-    implementation(libs.netty.buffer)
+    api(libs.netty.transport)
+    api(libs.netty.buffer)
     implementation(libs.netty.common)
-    implementation(libs.netty.codec.haproxy)
+    api(libs.netty.codec.haproxy)
     implementation(libs.netty.codec.http)
-    implementation(libs.netty.resolver.dns)
+    api(libs.netty.resolver.dns)
     implementation(libs.netty.transport.native.epoll)
     implementation(libs.netty.handler.proxy)
-    implementation(libs.bookkeeper.common)
+    api(libs.bookkeeper.common)
 
     compileOnly(libs.swagger.annotations)
 
diff --git a/pulsar-testclient/build.gradle.kts 
b/pulsar-testclient/build.gradle.kts
index 54637f9c0e7..9fd025af6f1 100644
--- a/pulsar-testclient/build.gradle.kts
+++ b/pulsar-testclient/build.gradle.kts
@@ -23,20 +23,20 @@ plugins {
 }
 
 dependencies {
-    implementation(libs.slog)
+    api(libs.slog)
     implementation(project(":pulsar-client-original"))
     implementation(project(":pulsar-client-admin-original"))
-    implementation(project(":pulsar-client-api-v5"))
+    api(project(":pulsar-client-api-v5"))
     implementation(project(":pulsar-client-v5"))
     implementation(project(":pulsar-client-messagecrypto-bc"))
     implementation(project(":pulsar-broker"))
-    implementation(project(":pulsar-cli-utils"))
+    api(project(":pulsar-cli-utils"))
     implementation(project(":pulsar-common"))
     implementation(project(":managed-ledger"))
-    implementation(libs.picocli)
-    implementation(libs.hdrHistogram)
+    api(libs.picocli)
+    api(libs.hdrHistogram)
     implementation(libs.jackson.databind)
-    implementation(libs.guava)
+    api(libs.guava)
     implementation(libs.commons.lang3)
     implementation(libs.commons.codec)
     implementation(libs.slf4j.api)
@@ -45,11 +45,11 @@ dependencies {
     implementation(libs.zookeeper) {
         exclude(group = "org.slf4j")
     }
-    implementation(libs.bookkeeper.server)
+    api(libs.bookkeeper.server)
     implementation(libs.re2j)
-    implementation(libs.gson)
+    api(libs.gson)
     implementation(libs.jetty.client)
-    implementation(libs.jetty.websocket.jetty.api)
+    api(libs.jetty.websocket.jetty.api)
     implementation(libs.jetty.websocket.jetty.client)
     implementation(libs.jetty.util)
     implementation(libs.opentelemetry.sdk.extension.autoconfigure)
diff --git a/pulsar-transaction/coordinator/build.gradle.kts 
b/pulsar-transaction/coordinator/build.gradle.kts
index 9223abfc712..a8af0c1b02a 100644
--- a/pulsar-transaction/coordinator/build.gradle.kts
+++ b/pulsar-transaction/coordinator/build.gradle.kts
@@ -26,13 +26,13 @@ dependencies {
     implementation(libs.slog)
     api(project(":pulsar-common"))
     implementation(project(":pulsar-opentelemetry"))
-    implementation(project(":managed-ledger"))
+    api(project(":managed-ledger"))
     implementation(libs.commons.lang3)
     implementation(libs.commons.collections4)
-    implementation(libs.netty.buffer)
-    implementation(libs.netty.common)
+    api(libs.netty.buffer)
+    api(libs.netty.common)
     implementation(libs.jctools.core.jdk11)
-    implementation(libs.simpleclient)
+    api(libs.simpleclient)
     implementation(libs.guava)
     implementation(libs.bookkeeper.server)
 
diff --git a/pulsar-websocket/build.gradle.kts 
b/pulsar-websocket/build.gradle.kts
index aae83d0db43..ee2b5d7e6e5 100644
--- a/pulsar-websocket/build.gradle.kts
+++ b/pulsar-websocket/build.gradle.kts
@@ -24,7 +24,7 @@ plugins {
 dependencies {
     implementation(libs.slog)
     api(project(":pulsar-broker-common"))
-    implementation(project(":pulsar-common"))
+    api(project(":pulsar-common"))
     implementation(project(":pulsar-client-original"))
     implementation(project(":pulsar-docs-tools"))
     implementation(libs.commons.lang3)
@@ -39,10 +39,10 @@ dependencies {
     // ee10 + jakarta.servlet for the REST/admin tier (Jersey 3) and the 
WebSocket endpoint layer
     implementation(libs.jetty.ee10.servlet)
     implementation(libs.jetty.ee10.servlets)
-    implementation(libs.jakarta.servlet.api)
+    api(libs.jakarta.servlet.api)
     // Modern Jetty 12 WebSocket API on the ee10 stack (PIP-472)
-    implementation(libs.jetty.ee10.websocket.jetty.server)
-    implementation(libs.jetty.websocket.jetty.api)
+    api(libs.jetty.ee10.websocket.jetty.server)
+    api(libs.jetty.websocket.jetty.api)
     implementation(libs.jetty.websocket.jetty.client)
     implementation(libs.hdrHistogram)
     implementation(libs.picocli)
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 7c5eba5a6b4..c847d5be210 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -68,10 +68,6 @@ include("pulsar-bom")
 
 // Tier 0 — no internal dependencies
 include("buildtools")
-// Maven artifactId is "bouncy-castle-bc" (directory is "bouncy-castle/bc")
-include("bouncy-castle:bouncy-castle-bc")
-project(":bouncy-castle:bouncy-castle-bc").projectDir = 
file("bouncy-castle/bc")
-include("bouncy-castle:bcfips")
 include("pulsar-config-validation")
 include("pulsar-client-api")
 include("pulsar-client-api-v5")
@@ -180,8 +176,9 @@ 
project(":jetty-upgrade:pulsar-zookeeper-prometheus-metrics").projectDir = file(
 include("jetty-upgrade:zookeeper-with-patched-admin")
 project(":jetty-upgrade:zookeeper-with-patched-admin").projectDir = 
file("jetty-upgrade/zookeeper-with-patched-admin")
 
-// Tier 6.5 — bouncy castle test
-include("bouncy-castle:bcfips-include-test")
+// Tier 6.5 — FIPS BouncyCastle TLS integration test
+include("tests:pulsar-client-test-bcfips")
+project(":tests:pulsar-client-test-bcfips").projectDir = 
file("tests/pulsar-client-test-bcfips")
 
 // Tier 7
 include("pulsar-proxy")
diff --git a/tests/docker-images/java-test-functions/build.gradle.kts 
b/tests/docker-images/java-test-functions/build.gradle.kts
index c94069cf976..c0df4956809 100644
--- a/tests/docker-images/java-test-functions/build.gradle.kts
+++ b/tests/docker-images/java-test-functions/build.gradle.kts
@@ -24,8 +24,8 @@ plugins {
 
 dependencies {
     implementation(libs.slog)
-    implementation(project(":pulsar-io:pulsar-io-core"))
-    implementation(project(":pulsar-functions:pulsar-functions-api"))
+    api(project(":pulsar-io:pulsar-io-core"))
+    api(project(":pulsar-functions:pulsar-functions-api"))
     compileOnly(libs.avro)
     compileOnly(libs.jackson.databind)
     compileOnly(libs.protobuf.java)
diff --git a/tests/pulsar-client-all-shade-test/build.gradle.kts 
b/tests/pulsar-client-all-shade-test/build.gradle.kts
index 48c7260d96b..bd3f111ea17 100644
--- a/tests/pulsar-client-all-shade-test/build.gradle.kts
+++ b/tests/pulsar-client-all-shade-test/build.gradle.kts
@@ -30,9 +30,9 @@ dependencies {
     testImplementation(project(":pulsar-client-api"))
     testImplementation(project(":pulsar-client-admin-api"))
     testImplementation(project(":pulsar-client-messagecrypto-bc"))
-    testImplementation(project(":bouncy-castle:bouncy-castle-bc"))
     testImplementation(project(":buildtools"))
     testImplementation(libs.bcprov.jdk18on)
+    testImplementation(libs.bcpkix.jdk18on)
     testImplementation(libs.testcontainers)
     // Runtime deps needed by the client that are not bundled in the shaded 
JARs
     testRuntimeOnly(libs.opentelemetry.api)
diff --git a/bouncy-castle/bcfips-include-test/build.gradle.kts 
b/tests/pulsar-client-test-bcfips/build.gradle.kts
similarity index 75%
rename from bouncy-castle/bcfips-include-test/build.gradle.kts
rename to tests/pulsar-client-test-bcfips/build.gradle.kts
index d52407e2d4b..836e1382b99 100644
--- a/bouncy-castle/bcfips-include-test/build.gradle.kts
+++ b/tests/pulsar-client-test-bcfips/build.gradle.kts
@@ -22,11 +22,11 @@ plugins {
     id("pulsar.test-certs-conventions")
 }
 
-
-// Exclude the non-FIPS BouncyCastle module — this module tests with FIPS 
provider only.
-// Having both bc (bcprov) and bcfips (bc-fips) causes CryptoServicesRegistrar 
conflicts.
+// FIPS is selected by EXCLUDING the non-FIPS BouncyCastle libraries 
(bcprov/bcpkix/bcutil) that the
+// broker and client pull in transitively, leaving only the FIPS provider 
(bc-fips) on the classpath.
+// The non-FIPS and FIPS jars both define org.bouncycastle.* classes with 
different signers, so having
+// both on one classpath triggers CryptoServicesRegistrar signer conflicts.
 configurations.all {
-    exclude(group = "org.apache.pulsar", module = "bc")
     exclude(group = "org.bouncycastle", module = "bcprov-jdk18on")
     exclude(group = "org.bouncycastle", module = "bcprov-ext-jdk18on")
     exclude(group = "org.bouncycastle", module = "bcpkix-jdk18on")
@@ -35,7 +35,9 @@ configurations.all {
 
 dependencies {
     implementation(libs.slog)
-    testImplementation(project(":bouncy-castle:bcfips"))
+    testImplementation(libs.bc.fips)
+    testImplementation(libs.bcpkix.fips)
+    testImplementation(libs.bcutil.fips)
     testImplementation(project(":pulsar-common"))
     testImplementation(project(":pulsar-broker"))
     testImplementation(project(path = ":pulsar-broker", configuration = 
"testJar"))
diff --git 
a/bouncy-castle/bcfips-include-test/src/test/java/org/apache/pulsar/client/TlsProducerConsumerBase.java
 
b/tests/pulsar-client-test-bcfips/src/test/java/org/apache/pulsar/client/TlsProducerConsumerBase.java
similarity index 100%
rename from 
bouncy-castle/bcfips-include-test/src/test/java/org/apache/pulsar/client/TlsProducerConsumerBase.java
rename to 
tests/pulsar-client-test-bcfips/src/test/java/org/apache/pulsar/client/TlsProducerConsumerBase.java
diff --git 
a/bouncy-castle/bcfips-include-test/src/test/java/org/apache/pulsar/client/TlsProducerConsumerTest.java
 
b/tests/pulsar-client-test-bcfips/src/test/java/org/apache/pulsar/client/TlsProducerConsumerTest.java
similarity index 100%
rename from 
bouncy-castle/bcfips-include-test/src/test/java/org/apache/pulsar/client/TlsProducerConsumerTest.java
rename to 
tests/pulsar-client-test-bcfips/src/test/java/org/apache/pulsar/client/TlsProducerConsumerTest.java
diff --git a/tiered-storage/file-system/build.gradle.kts 
b/tiered-storage/file-system/build.gradle.kts
index 57982d45222..93d2d8c1c80 100644
--- a/tiered-storage/file-system/build.gradle.kts
+++ b/tiered-storage/file-system/build.gradle.kts
@@ -60,7 +60,7 @@ dependencies {
     compileOnly(project(":managed-ledger"))
     compileOnly(libs.bookkeeper.server)
     compileOnly(libs.netty.buffer)
-    implementation(libs.hadoop.common) {
+    api(libs.hadoop.common) {
         exclude(group = "log4j", module = "log4j")
         exclude(group = "org.slf4j")
         exclude(group = "dnsjava", module = "dnsjava")

Reply via email to