ory/hydra · error
unable to read body
Error message
unable to read body
What it means
requestBody buffers the HTTP request body so it can be re-read by multiple decoders. If io.ReadAll fails (connection reset, client abort, body closed early), the read error is wrapped with 'unable to read body'.
Source
Thrown at oryx/decoderx/http.go:294
} else if httpx.HasContentType(r, httpContentTypeMultipartForm, httpContentTypeURLEncodedForm) {
return decodeForm(r, destination, c)
}
return errors.WithStack(herodot.ErrInternalServerError().WithReasonf("Unable to determine decoder for content type: %s", r.Header.Get("Content-Type")))
}
func requestBody(r *http.Request, o *httpDecoderOptions) (reader io.ReadCloser, err error) {
if strings.ToUpper(r.Method) == "GET" {
return io.NopCloser(bytes.NewBufferString(r.URL.Query().Encode())), nil
}
if !o.keepRequestBody {
return r.Body, nil
}
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
return nil, errors.Wrapf(err, "unable to read body")
}
_ = r.Body.Close() // must close
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
return io.NopCloser(bytes.NewBuffer(bodyBytes)), nil
}
func decodeJSONForm(r *http.Request, destination interface{}, o *httpDecoderOptions) error {
if o.jsonSchemaCompiler == nil {
return errors.WithStack(herodot.ErrInternalServerError().WithReasonf("Unable to decode HTTP Form Body because no validation schema was provided. This is a code bug."))
}
paths, err := jsonschemax.ListPathsWithRecursion(r.Context(), o.jsonSchemaRef, o.jsonSchemaCompiler, o.maxCircularReferenceDepth)
if err != nil {
return errors.WithStack(herodot.ErrInternalServerError().WithTrace(err).WithReasonf("Unable to prepare JSON Schema for HTTP Post Body Form parsing: %s", err).WithDebugf("%+v", err))
}
View on GitHub (pinned to 4174065ffb)
Solutions
- Check client-side network stability and increase client/timeout limits for large payloads.
- If the body was already read by other middleware, restore it (io.NopCloser(bytes.NewBuffer(bytes))) or rely on keepRequestBody buffering.
- In tests, always pass a valid io.Reader to http.NewRequest (e.g. strings.NewReader("{}")) instead of nil.
Example fix
// before
req, _ := http.NewRequest("POST", url, nil) // body read fails
// after
req, _ := http.NewRequest("POST", url, strings.NewReader(`{"key":"value"}`)) Defensive patterns
Strategy: try-catch
Validate before calling
if r.Body == nil {
return errors.New("request body is nil")
} Try / catch
if err := decoderx.Decode(ctx, r, &dest, opts); err != nil {
if strings.Contains(err.Error(), "unable to read body") {
return http.StatusBadRequest // client aborted or body unreadable
}
return http.StatusInternalServerError
} Prevention
- Never read r.Body before the decoder unless you restore it with io.NopCloser(bytes.NewBuffer(...)).
- Set reasonable client timeouts and request size limits so large uploads aren't aborted.
- Return 400 rather than 500 when the body read fails — it's usually a client-side issue.
When it happens
Trigger: Calling decodeJSON, decodeJSONForm, or decodeForm (via requestBody) when the request Body reader returns an error: client disconnected mid-upload, transport errors, or an already-consumed/closed body without keepRequestBody handling.
Common situations: Large JSON payloads where clients time out and abort; proxies terminating requests; tests passing a request with a closed or nil body; reading r.Body twice in custom middleware.
Related errors
- Failed to read response: %s
- errKeyNotFound
- http(s) loader disabled
- can not serve request over insecure http
- %s content encoding not supported
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/f3449d9b165d1228.
Report an issue: GitHub.