quarkusio/quarkus · error · IllegalStateException
QuarkusMultipartResponseDecoder was destroyed already
Error message
QuarkusMultipartResponseDecoder was destroyed already
What it means
checkDestroyed() throws IllegalStateException when any public operation (offer, hasNext, next, getBodyHttpDatas, getBodyHttpData, isMultipart, etc.) is called after destroy() has run. The decoder is a stateful, closeable object and cannot be reused after being torn down.
Source
Thrown at independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartResponseDecoder.java:252
currentStatus = MultiPartStatus.HEADERDELIMITER;
try {
if (this.response instanceof HttpContent) {
// Offer automatically if the given request is als type of HttpContent
// See #1089
offer((HttpContent) this.response);
} else {
parseBody();
}
} catch (Throwable e) {
destroy();
PlatformDependent.throwException(e);
}
}
private void checkDestroyed() {
if (destroyed) {
throw new IllegalStateException(QuarkusMultipartResponseDecoder.class.getSimpleName()
+ " was destroyed already");
}
}
/**
* True if this request is a Multipart request
*
* @return True if this request is a Multipart request
*/
public boolean isMultipart() {
checkDestroyed();
return true;
}
/**
* Set the amount of bytes after which read bytes in the buffer should be discarded.
* Setting this lower gives lower memory usage but with the overhead of more memory copies.
* Use {@code 0} to disable it.View on GitHub (pinned to e1c734241f)
Solutions
- Create a new QuarkusMultipartResponseDecoder per response; never reuse or cache instances
- Guard usage with isDestroyed() (or your own flag) before further calls
- Ensure lifecycle code does not call destroy() while more chunks may still arrive
- Restructure async handlers so the decoder reference is dropped once the response completes
Example fix
// before
this.decoder.offer(chunk); // decoder may already be destroyed
// after
if (!decoder.isDestroyed()) {
decoder.offer(chunk);
} else {
decoder = new QuarkusMultipartResponseDecoder(response, factory, charset);
decoder.offer(chunk);
} Defensive patterns
Strategy: type-guard
Validate before calling
if (decoder.isDestroyed()) { decoder = createNewDecoder(); } Type guard
boolean usable(QuarkusMultipartResponseDecoder d) { return d != null && !d.isDestroyed(); } Try / catch
try { decoder.offer(chunk); } catch (IllegalStateException e) { if (e.getMessage().contains("destroyed")) { decoder = createNewDecoder(); decoder.offer(chunk); } else throw e; } Prevention
- One decoder instance per response; never cache across requests
- Null out the decoder reference in the response-completion handler
- Wrap multi-callback handling in a lifecycle owner that checks destroyed state
When it happens
Trigger: Calling offer()/hasNext()/next()/getBodyHttpData(s) on a decoder instance after destroy() was called — typically by continuing to use a decoder stored in a field after response completion, or on error paths that call destroy().
Common situations: Reusing a cached decoder across requests; async callbacks firing after the response was cleaned up; decoding an errored response that triggered destroy() internally (e.g. missing boundary) and then probing its state.
Related errors
- Cannot reset a started application
- Cannot add value once finalized
- Header already encoded
- Needs a boundary value
- No 'Content-Type' header present.
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/4e323c013878a0b4.
Report an issue: GitHub.