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
- Build the new headers set separately: create a mutable HttpHeaders and copy the read-only entries into it (e.g. request.headers().setAll(readOnlyHeaders)).
- For per-request customization, start from request.headers() (mutable) and add base headers into it, never mutate the base template.
- Check the mutability contract before mutating; in Vert.x use a fresh HttpHeaders created via the factory instead of reusing response headers.
- 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
- Treat headers() from a completed exchange as immutable; copy before editing.
- Customize request.headers() before sending instead of editing response headers.
- Keep immutable base-header templates separate from per-request mutable copies.
- Pass mutable HttpHeaders explicitly when helpers need to mutate.
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
- 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/45a99a88d366dfea.
Report an issue: GitHub.