eclipse-vertx/vert.x · error · java.lang.IllegalStateException

Read only

Error message

Read only

What it means

The default HttpHeaders implementation (io.vertx.core.http.impl.headers.HttpHeaders) guards every mutating method with a mutable flag. add(String, String) throws IllegalStateException("Read only") when the instance is read-only, e.g. when you hold headers obtained from a completed response or an immutable shared constant. The object is fine for reading; mutation requires a mutable instance.

Source

Thrown at vertx-core/src/main/java/io/vertx/core/http/impl/headers/HttpHeaders.java:123

  @Override
  public boolean isEmpty() {
    return headers.isEmpty();
  }

  @Override
  public Set<String> names() {
    Set<String> names = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
    for (Map.Entry<CharSequence, CharSequence> header : headers) {
      names.add(header.getKey().toString());
    }
    return names;
  }

  @Override
  public HttpHeaders add(String name, String value) {
    if (!mutable) {
      throw new IllegalStateException("Read only");
    }
    if (!HttpHeadersInternal.DISABLE_HTTP_HEADERS_VALIDATION) {
      HttpUtils.validateHeader(name, value);
    }
    headers.add(HttpUtils.toLowerCase(name), value);
    return this;
  }

  @Override
  public HttpHeaders add(String name, Iterable<String> values) {
    if (!mutable) {
      throw new IllegalStateException("Read only");
    }
    if (!HttpHeadersInternal.DISABLE_HTTP_HEADERS_VALIDATION) {
      HttpUtils.validateHeader(name, values);
    }
    headers.add(HttpUtils.toLowerCase(name), values);
    return this;

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Build the new headers set separately: create a mutable HttpHeaders and copy the read-only entries into it (e.g. request.headers().setAll(readOnlyHeaders)).
  2. For per-request customization, start from request.headers() (mutable) and add base headers into it, never mutate the base template.
  3. Check the mutability contract before mutating; in Vert.x use a fresh HttpHeaders created via the factory instead of reusing response headers.
  4. If you own the code that produced the read-only headers, create them mutable (or wrap with a mutable copy) at construction time.

Example fix

// before
response.headers().add("X-Extra", "v"); // Read only
// after
HttpHeaders extra = HttpHeaders.setAll(response.headers());
extra.add("X-Extra", "v");
Defensive patterns

Strategy: validation

Validate before calling

// Track mutability at the call site: only call add on headers you created
HttpHeaders target = HttpHeaders.setAll(response.headers()); // copy first
target.add("X-Extra", "v");

Try / catch

try { headers.add(name, value); } catch (IllegalStateException e) { HttpHeaders copy = HttpHeaders.setAll(headers); copy.add(name, value); }

Prevention

When it happens

Trigger: Calling add/addAll on a read-only HttpHeaders — commonly response.headers() after the response completed, or HttpHeaders objects shared as base headers (setBaseHeaders) that were created immutable.

Common situations: Reusing one HttpHeaders template for many requests and calling addAll on it (addAll mutates the target, not the source); copying response headers into a new request by mutating the response's headers object directly.

Related errors


AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06). Data as JSON: /api/errors/45a99a88d366dfea. Report an issue: GitHub.