apache/pulsar · error · URISyntaxException
No usable service URL (useTls=${useTls}, serviceUrl=${servic
Error message
No usable service URL (useTls=${useTls}, serviceUrl=${serviceUrl}, serviceUrlTls=${serviceUrlTls}) What it means
When a topic is migrated, the broker sends a service URL for the new cluster; setRedirectedClusterURI picks the TLS or non-TLS URL based on client config. If the selected URL is blank (e.g. a non-TLS client pointed at a TLS-only endpoint), it throws URISyntaxException with a descriptive reason instead of letting new URI(null) throw an opaque NPE.
Source
Thrown at pulsar-client/src/main/java/org/apache/pulsar/client/impl/HandlerState.java:61
Terminated, // Topic associated with this handler
// has been terminated
Failed, // Handler is failed
RegisteringSchema, // Handler is registering schema
ProducerFenced, // The producer has been fenced by the broker
};
public HandlerState(PulsarClientImpl client, String topic) {
this.client = client;
this.topic = topic;
STATE_UPDATER.set(this, State.Uninitialized);
}
protected void setRedirectedClusterURI(String serviceUrl, String serviceUrlTls) throws URISyntaxException {
String url = client.conf.isUseTls() && StringUtils.isNotBlank(serviceUrlTls) ? serviceUrlTls : serviceUrl;
if (StringUtils.isBlank(url)) {
// e.g. a non-TLS client given a TLS-only endpoint (or vice versa). Surface a clear,
// catchable error rather than letting new URI(null) throw an NPE.
throw new URISyntaxException(String.valueOf(url),
"No usable service URL (useTls=" + client.conf.isUseTls()
+ ", serviceUrl=" + serviceUrl + ", serviceUrlTls=" + serviceUrlTls + ")");
}
this.redirectedClusterURI = new URI(url);
}
// moves the state to ready if it wasn't closed
protected boolean changeToReadyState() {
if (STATE_UPDATER.get(this) == State.Ready) {
return true;
}
return (STATE_UPDATER.compareAndSet(this, State.Uninitialized, State.Ready)
|| STATE_UPDATER.compareAndSet(this, State.Connecting, State.Ready)
|| STATE_UPDATER.compareAndSet(this, State.RegisteringSchema, State.Ready));
}
protected boolean compareAndSetState(State expect, State update) {
return STATE_UPDATER.compareAndSet(this, expect, update);View on GitHub (pinned to 820761864e)
Solutions
- Enable TLS on the client (PulsarClientImpl builder useTls(true) / serviceUrl pulsar+ssl://) when the target only offers serviceUrlTls.
- Fix the destination cluster configuration so it publishes both serviceUrl and serviceUrlTls.
- Catch URISyntaxException in migration handling and fail over manually to the available URL.
Example fix
// before
PulsarClient client = PulsarClient.builder().serviceUrl("pulsar://old:6650").build(); // TLS-only target
// after
PulsarClient client = PulsarClient.builder().serviceUrl("pulsar+ssl://old:6651").enableTls(true).build(); Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check matching URL availability when handling migration metadata
boolean usable = client.getConfiguration().isUseTls()
? StringUtils.isNotBlank(serviceUrlTls) : StringUtils.isNotBlank(serviceUrl); Try / catch
try {
handler.handleTopicMigrated(...);
} catch (URISyntaxException e) {
// no URL matching client TLS mode: reconnect to original or fail over manually
} Prevention
- Ensure migrated clusters publish both pulsar:// and pulsar+ssl:// service URLs.
- Keep client useTls consistent with the endpoints published by the cluster.
- Handle topic-migration events explicitly in client wrappers.
When it happens
Trigger: handleTopicMigrated receives migration metadata where the URL matching the client's useTls setting is null/empty: a TLS client on a topic migrated to a cluster with no serviceUrlTls, or a plain client on a TLS-only migration target.
Common situations: Cluster migration where the new cluster publishes only one of the two URLs; client TLS misconfiguration (useTls not matching available endpoint); partial broker configuration on the destination cluster.
Related errors
- The replication cluster does not provide TLS encrypted servi
- Failed to acquire TLS material for purpose ${purpose}
- the PulsarTlsFactory passed to tlsFactory(...) has already b
- tlsPolicy purpose and policy must not be null
- tlsFactory must not be null
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/454cdf7d74444b12.
Report an issue: GitHub.