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
- Create a mutable copy first: new HeadersMultiMap().addAll(readOnlyHeaders) or HttpHeaders with an empty MultiMap and copy entries
- If you own the adaptor construction, create it with mutable=true
- Use the underlying object's own mutable API if the wrapper is intentionally read-only
- 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
- Treat request.headers() style views as immutable; copy before mutating
- Document ownership of MultiMap instances in middleware chains
- Centralize header mutation in helpers that always work on a fresh mutable MultiMap
- Never reuse cached header snapshots as scratch buffers
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
- a header value contains a prohibited character '127': <value
- a header value must not end with '\r' or '\n':<seq>
- a header value contains a prohibited character '127': <seq>
- only '\n' is allowed after '\r': <seq>
- only ' ' and '\t' are allowed after '\n': <seq>
AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06).
Data as JSON: /api/errors/9e820240681e6f67.
Report an issue: GitHub.