risingwavelabs/risingwave · error

topic name cannot be empty

Error message

topic name cannot be empty

What it means

The final component of a Pulsar topic (the topic name itself) must be non-empty. parse_topic constructs the Topic struct and then rejects it if the topic field is an empty string, since an empty leaf name is not addressable on the broker.

Solutions

  1. Remove the trailing slash and supply a real topic name
  2. Use 'persistent://public/default/mytopic' style input
  3. Trim the topic string before passing it to the connector

Example fix

// before
let topic = parse_topic("persistent://public/default/");
// after
let topic = parse_topic("persistent://public/default/mytopic");
Defensive patterns

Strategy: validation

Validate before calling

function validateNonEmptyLeaf(topic) {
  const leaf = (topic.split('://')[1] ?? topic).split('/').pop();
  if (!leaf) throw new Error(`topic name cannot be empty: ${topic}`);
}

Type guard

const hasNonEmptyLeaf = (t) => Boolean((t.split('://')[1] ?? t).split('/').pop());

Try / catch

try { parse_topic(input); } catch (e) { if (String(e).includes('topic name cannot be empty')) { stripTrailingSlashAndRetry(input); } else { throw e; } }

Prevention

When it happens

Trigger: Calling parse_topic with a URL ending in '/', e.g. 'persistent://public/default/' or 'persistent://public/default//', producing parts[2] == "".

Common situations: Trailing slash left over from copy-pasting a broker console URL, or programmatically building topic strings with template placeholders left blank.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/5bbdada96a7e7675. Report an issue: GitHub.

Appendix: source

Thrown at src/connector/src/source/pulsar/topic.rs:168

    let parts: Vec<&str> = rest.splitn(3, '/').collect();

    if parts.len() != 3 {
        bail!(
            "invalid topic name '{}'; it must be in the format <tenant>/<namespace>/<topic>",
            rest
        );
    }

    let parsed_topic = Topic {
        domain: domain.to_owned(),
        tenant: parts[0].to_owned(),
        namespace: parts[1].to_owned(),
        topic: parts[2].to_owned(),
        partition_index: get_partition_index(complete_topic.as_str())?,
    };

    if parsed_topic.topic.is_empty() {
        bail!("topic name cannot be empty".to_owned());
    }

    Ok(parsed_topic)
}

pub(crate) async fn check_topic_exists(
    client: &Pulsar<TokioExecutor>,
    topic: &Topic,
) -> Result<()> {
    // issue about api `get_topics_of_namespace`:
    // for partitioned topic, the api will return all sub-topic of the topic instead of the topic itself
    // Reduce async state machine size (see `clippy::large_futures`).
    let topics_on_broker = Box::pin(client.get_topics_of_namespace(
        format!("{}/{}", topic.tenant, topic.namespace),
        LookupMode::All,
    ))
    .await?;
    if !topics_on_broker.contains(&topic.to_string()) {

View on GitHub (pinned to 6469eb736d)