eclipse-vertx/vert.x · error · IllegalStateException

Read only

Error message

Read only

What it means

HeadersAdaptor wraps an existing HttpHeaders instance and can be created read-only (mutable=false). All mutating operations throw IllegalStateException("Read only") when the adaptor is not mutable. add(String,String) is one of those guarded mutators, invoked directly or via addAll/setAll.

Source

Thrown at vertx-core/src/main/java/io/vertx/core/http/impl/headers/HeadersAdaptor.java:72

  @Override
  public boolean contains(String name) {
    return headers.contains(name);
  }

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

  @Override
  public Set<String> names() {
    return headers.names();
  }

  @Override
  public HeadersAdaptor add(String name, String value) {
    if (!mutable) {
      throw new IllegalStateException("Read only");
    }
    headers.add(name, value);
    return this;
  }

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

  @Override
  public HeadersAdaptor addAll(MultiMap headers) {
    for (Map.Entry<String, String> entry: headers.entries()) {
      add(entry.getKey(), entry.getValue());

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Create a mutable copy first: new HeadersMultiMap().addAll(readOnlyHeaders) or HttpHeaders with an empty MultiMap and copy entries
  2. If you own the adaptor construction, create it with mutable=true
  3. Use the underlying object's own mutable API if the wrapper is intentionally read-only
  4. Don't expose read-only adaptors to code that mutates them; document ownership

Example fix

// before
readOnlyHeaders.add("X-Custom", "v"); // IllegalStateException
// after
io.vertx.core.MultiMap mutable = io.vertx.core.MultiMap.caseInsensitiveMultiMap();
mutable.addAll(readOnlyHeaders);
mutable.add("X-Custom", "v");
Defensive patterns

Strategy: validation

Validate before calling

if (!isMutable(headers)) {
  headers = io.vertx.core.MultiMap.caseInsensitiveMultiMap().addAll(headers);
}
headers.add("X-Custom", "v");

Type guard

static boolean isMutable(io.vertx.core.MultiMap m) { return !(m instanceof io.vertx.core.http.impl.headers.HeadersAdaptor) || true; // adaptor exposes no mutability flag; treat unknown/IO-sourced views as read-only

Try / catch

try {
  headers.add(name, value);
} catch (IllegalStateException e) {
  if ("Read only".equals(e.getMessage())) {
    headers = io.vertx.core.MultiMap.caseInsensitiveMultiMap().addAll(headers);
    headers.add(name, value);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling headersMultiMap.add("k","v") on an adaptor obtained from a read-only source, e.g. request.headers() style views built as new HeadersAdaptor(headers, false), or calling addAll/add on a shared immutable headers view.

Common situations: Trying to modify response/request headers through a cached or shared read-only headers snapshot; wrapping server-supplied headers with a non-mutable adaptor and then attempting to append a value; framework code (addAll/setAll) delegating into add and hitting the guard.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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