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

  1. Check client-side network stability and increase client/timeout limits for large payloads.
  2. If the body was already read by other middleware, restore it (io.NopCloser(bytes.NewBuffer(bytes))) or rely on keepRequestBody buffering.
  3. 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

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


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/f3449d9b165d1228. Report an issue: GitHub.