eclipse-vertx/vert.x · error · IllegalStateException

Read only

Error message

Read only

What it means

Http1xHeaders is an HTTP/1.x headers implementation backed by a copy-on-write structure. MapEntry.setValue(String) throws IllegalStateException("Read only") when the headers are flagged readOnly, and also throws ConcurrentModificationException if the iterator's expectedModCount is stale.

Source

Thrown at vertx-core/src/main/java/io/vertx/core/http/impl/headers/Http1xHeaders.java:403

            this.index = index;
            this.entry = entry;
            this.expectedModCount = expectedModCount;
          }

          @Override
          public String getKey() {
            return entry.key.toString();
          }

          @Override
          public String getValue() {
            return entry.value.toString();
          }

          @Override
          public String setValue(String value) {
            if (readOnly) {
              throw new IllegalStateException("Read only");
            }
            if (expectedModCount != modCount) {
              throw new ConcurrentModificationException();
            }
            if (ref != null) {
              copyOnWrite();
              int i = 0;
              MapEntry e = head;
              for (;i < index; i++) {
                e = e.after;
              }
              entry = e;
              MapEntry c = null;
              while (++i < numberOfIterations) {
                e = e.after;
                c = e;
              }
              int modCountValue = ++modCount;

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Create a mutable copy of the headers and edit entries there
  2. Perform entry mutations before the HTTP message is finalized
  3. Recreate the headers object for the next lifecycle instead of mutating the frozen one
  4. Check readOnly (or catch IllegalStateException) before mutating through the iterator

Example fix

// before
for (Map.Entry<String,String> e : headers) { e.setValue(lower(e.getValue())); } // may be read-only
// after
MultiMap copy = MultiMap.caseInsensitiveMultiMap().addAll(headers);
for (Map.Entry<String,String> e : copy.entries()) { e.setValue(lower(e.getValue())); }
Defensive patterns

Strategy: try-catch

Validate before calling

if (headers instanceof Http1xHeaders h && h.size() > 0 && isFrozenLifecycle()) { /* do not mutate via entry.setValue */ }

Try / catch

try { entry.setValue(newValue); } catch (IllegalStateException e) { /* read-only: rebuild headers */ MultiMap copy = MultiMap.caseInsensitiveMultiMap().addAll(headers); copy.set(entry.getKey(), newValue); } catch (ConcurrentModificationException e) { /* re-acquire iterator */ }

Prevention

When it happens

Trigger: Mutating an entry via entry.setValue(...) obtained from an iterator over an Http1xHeaders instance that has been marked read-only (e.g. frozen after the request/response completed or shared as immutable).

Common situations: Rewriting header values while iterating over finished-response headers; in-place header rewriting after the exchange completed; reusing iterators across lifecycle phases.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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