apache/druid · error · ISE

Bad URL: %s

Error message

Bad URL: %s

What it means

HttpPostEmitter's constructor validates config.getRecipientBaseUrl() by constructing a java.net.URL. If the URL is malformed (MalformedURLException), it throws IllegalStateException wrapping the exception with 'Bad URL: <url>'. The emitter cannot deliver events without a syntactically valid recipient URL.

Source

Thrown at processing/src/main/java/org/apache/druid/java/util/emitter/core/HttpPostEmitter.java:176

        config.getMaxBatchSize() >= MAX_EVENT_SIZE + batchOverhead,
        StringUtils.format(
            "maxBatchSize must be greater than MAX_EVENT_SIZE[%,d] + overhead[%,d].",
            MAX_EVENT_SIZE,
            batchOverhead
        )
    );
    this.config = config;
    this.bufferSize = config.getMaxBatchSize();
    this.maxBufferWatermark = bufferSize - batchingStrategy.batchEndLength();
    // Chosen so that if event size < largeEventThreshold, at least 2 events could fit the standard buffer.
    this.largeEventThreshold = (bufferSize - batchOverhead - batchingStrategy.separatorLength()) / 2;
    this.client = client;
    this.jsonMapper = jsonMapper;
    try {
      this.url = new URL(config.getRecipientBaseUrl()).toString();
    }
    catch (MalformedURLException e) {
      throw new ISE(e, "Bad URL: %s", config.getRecipientBaseUrl());
    }
    emittingThread = new EmittingThread(config);
    long firstBatchNumber = 1;
    concurrentBatch.set(new Batch(this, acquireBuffer(), firstBatchNumber));
    // lastBatchFillTimeMillis must not be 0, minHttpTimeoutMillis could be.
    lastBatchFillTimeMillis = Math.max(config.minHttpTimeoutMillis, 1);
  }

  @Override
  @LifecycleStart
  public void start()
  {
    synchronized (startLock) {
      if (!running) {
        if (startLatch.getCount() == 0) {
          throw new IllegalStateException("Already started.");
        }
        running = true;

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Fix the recipientBaseUrl in config to a full absolute URL including scheme, e.g. http://host:8080/druid/metrics
  2. Validate the URL with new URL(value) or URI.create(value) before passing it into config
  3. Check for unresolved environment placeholders or stray whitespace in the property value

Example fix

// before
String url = "metrics-host:8080/druid/metrics"; // throws Bad URL
// after
String url = "http://metrics-host:8080/druid/metrics";
Defensive patterns

Strategy: validation

Validate before calling

String baseUrl = config.getRecipientBaseUrl();
try {
  new java.net.URL(baseUrl);
} catch (java.net.MalformedURLException e) {
  throw new IllegalArgumentException("recipientBaseUrl must be an absolute URL, got: " + baseUrl);
}

Type guard

boolean isValidUrl(String s) {
  if (s == null) return false;
  try { new java.net.URL(s); return true; } catch (java.net.MalformedURLException e) { return false; }
}

Try / catch

try {
  Emitter e = new HttpPostEmitter(config, mapper);
} catch (IllegalStateException ex) {
  log.error(ex, "Check org.apache.druid.java.util.emitter.http.url value");
  throw ex;
}

Prevention

When it happens

Trigger: Constructing new HttpPostEmitter(config, jsonMapper) when recipientBaseUrl is not a valid absolute URL (missing scheme, illegal characters, etc.).

Common situations: URL missing the http:// scheme (e.g. 'metrics-host:8080/druid/metrics'); trailing spaces or embedded quotes from config; hostnames with underscores; config templating leaving placeholder text like ${METRICS_URL} unresolved.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/e348a240a6b1dbc8. Report an issue: GitHub.