jaegertracing/jaeger · error

invalid server URL %q: %w

Error message

invalid server URL %q: %w

What it means

newRawClient parses each configured server address with net/url before building the connection pool. This error means url.Parse itself rejected the string (control characters, invalid percent-escapes, malformed IPv6 brackets, etc.), so the client cannot be constructed at all. It is returned from NewClient and surfaces at storage initialization time, not at request time.

Source

Thrown at internal/storage/elasticsearch/esclient/transport.go:65

	// discoverNodes enables one-shot node discovery (sniffing) at startup.
	discoverNodes bool
	// logLevel selects the client log-level (debug/info/error); empty disables
	// client logging. logger is the destination for those logs.
	logLevel string
	logger   *zap.Logger
}

// newRawClient builds a rawClient that round-robins requests across servers,
// sending each through base.
func newRawClient(ctx context.Context, base http.RoundTripper, opts rawClientOptions) (*rawClient, error) {
	if len(opts.servers) == 0 {
		return nil, errors.New("no servers specified")
	}
	urls := make([]*url.URL, 0, len(opts.servers))
	for _, server := range opts.servers {
		u, err := url.Parse(server)
		if err != nil {
			return nil, fmt.Errorf("invalid server URL %q: %w", server, err)
		}
		// url.Parse accepts host:port or bare hosts as scheme/path-only URLs; the
		// pool needs a scheme and host, so reject those up front with a clear error.
		if u.Scheme == "" || u.Host == "" {
			return nil, fmt.Errorf("server URL %q must include a scheme and host, e.g. http://host:9200", server)
		}
		urls = append(urls, u)
	}
	// Retry is disabled to preserve the current admin-client behavior; the data
	// plane can opt into the pool's read retry when it adopts rawClient in Stage B.
	transportOpts := []elastictransport.Option{
		elastictransport.WithURLs(urls...),
		elastictransport.WithTransport(base),
		elastictransport.WithDisableRetry(),
	}
	if opts.compressRequestBody {
		// Defaults to gzip.DefaultCompression, matching the level the olivere
		// client used before it was retired. The pool gzips the body and sets

View on GitHub (pinned to 806f444784)

Solutions

  1. Print the exact server string from the error message and inspect it for stray characters (spaces, quotes, newlines, commas).
  2. Fix the URL to a valid form, e.g. http://host:9200 or http://[::1]:9200 for IPv6, and URL-escape any special characters.
  3. If passing multiple servers, provide them as separate list elements (or split on commas) rather than one concatenated string.
  4. If %-escapes are intended, percent-encode them correctly (e.g. %25 for a literal %).

Example fix

// before
servers: ["http://es-1:9200,http://es-2:9200"]
// after
servers:
  - http://es-1:9200
  - http://es-2:9200
Defensive patterns

Strategy: validation

Validate before calling

for _, s := range servers {
    u, err := url.Parse(strings.TrimSpace(s))
    if err != nil {
        return fmt.Errorf("invalid server URL %q: %w", s, err)
    }
    if u.Scheme == "" || u.Host == "" {
        return fmt.Errorf("server URL %q must include scheme and host", s)
    }
}

Type guard

func isValidServerURL(s string) bool {
    u, err := url.Parse(strings.TrimSpace(s))
    return err == nil && u.Scheme != "" && u.Host != ""
}

Try / catch

client, err := esclient.NewClient(...)
if err != nil {
    if strings.Contains(err.Error(), "invalid server URL") {
        log.Fatalf("bad ES server config: %v", err) // fail fast at startup, not at request time
    }
    return err
}

Prevention

When it happens

Trigger: Calling NewClient (or the tests that build raw clients) with a server string net/url cannot parse — e.g. a URL containing raw spaces or control characters, invalid %-encoding like http://host:9200/%zz, or unbalanced brackets in an IPv6 literal like http://[::1:9200. Comma-separated or quoted strings leaked in from misparsed config will also fail here.

Common situations: Env var SPAN_STORAGE_TYPE elasticsearch config with stray whitespace/newlines or surrounding quotes pasted into the servers list; Kubernetes env interpolation introducing control characters; hand-edited YAML producing 'http://es:9200,http://es2:9200' as a single element; shell quoting stripping characters like [ or ] from IPv6 addresses.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01). Data as JSON: /api/errors/27ca262c41c7fcdc. Report an issue: GitHub.