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 b33674948d5 [fix][client] PIP-468: V5 client validates serviceUrl
scheme (#25720)
b33674948d5 is described below
commit b33674948d596cd20f36ceb9256f6a14bdb1f017
Author: Matteo Merli <[email protected]>
AuthorDate: Fri May 8 10:30:09 2026 -0700
[fix][client] PIP-468: V5 client validates serviceUrl scheme (#25720)
---
.../pulsar/client/api/v5/PulsarClientBuilder.java | 10 +-
.../client/impl/v5/PulsarClientBuilderV5.java | 26 +++++
.../client/impl/v5/PulsarClientBuilderV5Test.java | 107 +++++++++++++++++++++
3 files changed, 141 insertions(+), 2 deletions(-)
diff --git
a/pulsar-client-api-v5/src/main/java/org/apache/pulsar/client/api/v5/PulsarClientBuilder.java
b/pulsar-client-api-v5/src/main/java/org/apache/pulsar/client/api/v5/PulsarClientBuilder.java
index 101355e1182..14dcf8a0cd6 100644
---
a/pulsar-client-api-v5/src/main/java/org/apache/pulsar/client/api/v5/PulsarClientBuilder.java
+++
b/pulsar-client-api-v5/src/main/java/org/apache/pulsar/client/api/v5/PulsarClientBuilder.java
@@ -41,10 +41,16 @@ public interface PulsarClientBuilder {
PulsarClient build() throws PulsarClientException;
/**
- * Set the Pulsar service URL (e.g., {@code pulsar://localhost:6650}).
+ * Set the Pulsar service URL — the broker's binary-protocol endpoint.
*
- * @param serviceUrl the Pulsar service URL to connect to
+ * <p>Must use {@code pulsar://} or {@code pulsar+ssl://}. The admin/web
+ * service URL ({@code http://...} / {@code https://...}) is NOT accepted.
+ *
+ * @param serviceUrl the Pulsar broker service URL to connect to,
+ * e.g. {@code pulsar://localhost:6650}
* @return this builder instance for chaining
+ * @throws IllegalArgumentException if {@code serviceUrl} is null, blank,
or
+ * does not use {@code pulsar://} / {@code pulsar+ssl://}
*/
PulsarClientBuilder serviceUrl(String serviceUrl);
diff --git
a/pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5.java
b/pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5.java
index ea6afd012db..07894ab43aa 100644
---
a/pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5.java
+++
b/pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5.java
@@ -57,6 +57,7 @@ final class PulsarClientBuilderV5 implements
PulsarClientBuilder {
@Override
public PulsarClientBuilder serviceUrl(String serviceUrl) {
+ validatePulsarServiceUrl(serviceUrl, "serviceUrl");
conf.setServiceUrl(serviceUrl);
return this;
}
@@ -91,6 +92,7 @@ final class PulsarClientBuilderV5 implements
PulsarClientBuilder {
conf.setNumIoThreads(policy.ioThreads());
conf.setNumListenerThreads(policy.callbackThreads());
if (policy.proxyServiceUrl() != null) {
+ validatePulsarServiceUrl(policy.proxyServiceUrl(),
"ConnectionPolicy.proxyServiceUrl");
conf.setProxyServiceUrl(policy.proxyServiceUrl());
if (policy.proxyProtocol() != null) {
conf.setProxyProtocol(
@@ -142,4 +144,28 @@ final class PulsarClientBuilderV5 implements
PulsarClientBuilder {
conf.setDescription(description);
return this;
}
+
+ /**
+ * Reject anything that isn't the broker binary protocol. The most common
+ * mistake is passing the admin/web service URL ({@code http://...}) where
a
+ * broker URL is expected — call that out specifically. The v4 client used
to
+ * silently fail far downstream with cryptic connection errors; here we
fail
+ * fast at configure time with a message the user can act on.
+ */
+ private static void validatePulsarServiceUrl(String url, String fieldName)
{
+ if (url == null || url.isBlank()) {
+ throw new IllegalArgumentException(fieldName + " must not be null
or blank");
+ }
+ if (url.startsWith("pulsar://") || url.startsWith("pulsar+ssl://")) {
+ return;
+ }
+ if (url.startsWith("http://") || url.startsWith("https://")) {
+ throw new IllegalArgumentException(fieldName + " must use the
broker binary protocol "
+ + "(pulsar:// or pulsar+ssl://); got '" + url + "'. This
looks like the admin/web "
+ + "service URL — pass the broker service URL instead
(typically port 6650, or "
+ + "6651 for TLS).");
+ }
+ throw new IllegalArgumentException(fieldName + " must use the broker
binary protocol "
+ + "(pulsar:// or pulsar+ssl://); got '" + url + "'.");
+ }
}
diff --git
a/pulsar-client-v5/src/test/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5Test.java
b/pulsar-client-v5/src/test/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5Test.java
new file mode 100644
index 00000000000..b8dc5202540
--- /dev/null
+++
b/pulsar-client-v5/src/test/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5Test.java
@@ -0,0 +1,107 @@
+/*
+ * 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.client.impl.v5;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+import org.apache.pulsar.client.api.v5.PulsarClient;
+import org.apache.pulsar.client.api.v5.PulsarClientBuilder;
+import org.apache.pulsar.client.api.v5.config.ConnectionPolicy;
+import org.testng.annotations.Test;
+
+/**
+ * Service-URL validation on the V5 client builder. The v5 client only speaks
the
+ * broker binary protocol, so {@code pulsar://} / {@code pulsar+ssl://} are the
+ * only valid schemes — anything else (especially the admin/web service URL)
gets
+ * rejected at configure-time with a message that points to the right URL.
+ */
+public class PulsarClientBuilderV5Test {
+
+ @Test
+ public void testAcceptsPulsarScheme() {
+ // Must not throw — these are the valid forms.
+ PulsarClient.builder().serviceUrl("pulsar://localhost:6650");
+ PulsarClient.builder().serviceUrl("pulsar+ssl://localhost:6651");
+ PulsarClient.builder().serviceUrl("pulsar://h1:6650,h2:6650,h3:6650");
+ }
+
+ @Test
+ public void testRejectsHttpWithGuidance() {
+ IllegalArgumentException e = assertThrowsIAE(() ->
+ PulsarClient.builder().serviceUrl("http://localhost:8080"));
+ assertTrue(e.getMessage().contains("pulsar://"),
+ "error must point at the correct scheme: " + e.getMessage());
+ assertTrue(e.getMessage().toLowerCase().contains("admin")
+ || e.getMessage().toLowerCase().contains("web"),
+ "error must call out the http→admin-URL confusion: " +
e.getMessage());
+ assertTrue(e.getMessage().contains("6650"),
+ "error must hint at the broker port: " + e.getMessage());
+ }
+
+ @Test
+ public void testRejectsHttpsWithGuidance() {
+ IllegalArgumentException e = assertThrowsIAE(() ->
+ PulsarClient.builder().serviceUrl("https://localhost:8443"));
+ assertTrue(e.getMessage().contains("pulsar+ssl://"),
+ "error must mention the TLS broker scheme: " + e.getMessage());
+ }
+
+ @Test
+ public void testRejectsUnknownScheme() {
+ IllegalArgumentException e = assertThrowsIAE(() ->
+ PulsarClient.builder().serviceUrl("ws://localhost:6650"));
+ assertTrue(e.getMessage().contains("pulsar://"),
+ "error must point at the correct scheme: " + e.getMessage());
+ }
+
+ @Test
+ public void testRejectsNullAndBlank() {
+ assertThrows(IllegalArgumentException.class,
+ () -> PulsarClient.builder().serviceUrl(null));
+ assertThrows(IllegalArgumentException.class,
+ () -> PulsarClient.builder().serviceUrl(""));
+ assertThrows(IllegalArgumentException.class,
+ () -> PulsarClient.builder().serviceUrl(" "));
+ }
+
+ @Test
+ public void testProxyServiceUrlIsValidatedToo() {
+ PulsarClientBuilder builder = PulsarClient.builder()
+ .serviceUrl("pulsar://localhost:6650");
+
+ ConnectionPolicy badProxy = ConnectionPolicy.builder()
+ .proxy("http://proxy:8080", null)
+ .build();
+
+ IllegalArgumentException e = assertThrowsIAE(() ->
builder.connectionPolicy(badProxy));
+ assertTrue(e.getMessage().contains("proxyServiceUrl"),
+ "error must name the offending field: " + e.getMessage());
+ }
+
+ private static IllegalArgumentException assertThrowsIAE(Runnable r) {
+ try {
+ r.run();
+ fail("expected IllegalArgumentException");
+ return null; // unreachable
+ } catch (IllegalArgumentException e) {
+ return e;
+ }
+ }
+}