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

  1. Enable TLS on the client (PulsarClientImpl builder useTls(true) / serviceUrl pulsar+ssl://) when the target only offers serviceUrlTls.
  2. Fix the destination cluster configuration so it publishes both serviceUrl and serviceUrlTls.
  3. 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

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


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/454cdf7d74444b12. Report an issue: GitHub.