kataras/iris · error

ErrNotFound

ErrNotFound

Error message

unmarshal: empty body: %w

What it means

Returned by Context.UnmarshalBody (the engine behind ctx.ReadJSON, ReadXML and custom Unmarshaler reads) when the request has no body at all (ctx.request.Body == nil). The library wraps the sentinel ErrNotFound, so callers can distinguish 'no body to unmarshal' from a decoding failure.

Source

Thrown at context/context.go:2707

	return body, nil
}

// Validator is the validator for request body on Context methods such as
// ReadJSON, ReadMsgPack, ReadXML, ReadYAML, ReadForm, ReadQuery, ReadBody and e.t.c.
type Validator interface {
	Struct(any) error
	// If community asks for more than a struct validation on JSON, XML, MsgPack, Form, Query and e.t.c
	// then we should add more methods here, alternative approach would be to have a
	// `Validator:Validate(any) error` and a map[reflect.Kind]Validator instead.
}

// UnmarshalBody reads the request's body and binds it to a value or pointer of any type
// Examples of usage: context.ReadJSON, context.ReadXML.
//
// Example: https://github.com/kataras/iris/blob/main/_examples/request-body/read-custom-via-unmarshaler/main.go
func (ctx *Context) UnmarshalBody(outPtr any, unmarshaler Unmarshaler) error {
	if ctx.request.Body == nil {
		return fmt.Errorf("unmarshal: empty body: %w", ErrNotFound)
	}

	rawData, err := ctx.GetBody()
	if err != nil {
		return err
	}

	if decoderWithCtx, ok := outPtr.(BodyDecoderWithContext); ok {
		return decoderWithCtx.DecodeContext(ctx.request.Context(), rawData)
	}

	// check if the v contains its own decode
	// in this case the v should be a pointer also,
	// but this is up to the user's custom Decode implementation*
	//
	// See 'BodyDecoder' for more.
	if decoder, isDecoder := outPtr.(BodyDecoder); isDecoder {
		return decoder.Decode(rawData)

View on GitHub (pinned to 7bedaf55a0)

Solutions

  1. Have clients always send a valid body for endpoints using ReadJSON/ReadXML (e.g. '{}').
  2. Check errors.Is(err, context.ErrNotFound) and return 400 'empty request body'.
  3. Use ctx.GetBody() once and decode manually if you need custom empty-body defaults.
  4. In tests, set req.Body = io.NopCloser(strings.NewReader(...)) instead of nil.

Example fix

// before
var p Product
if err := ctx.ReadJSON(&p); err != nil { return err }
// after
var p Product
if err := ctx.ReadJSON(&p); err != nil {
    if errors.Is(err, context.ErrNotFound) {
        ctx.StatusCode(iris.StatusBadRequest)
        return errors.New("request body is required")
    }
    return err
}
Defensive patterns

Strategy: validation

Validate before calling

if ctx.Request().ContentLength == 0 {
    return errors.New("request body is required")
}

Type guard

func hasRequestBody(ctx *context.Context) bool {
    return ctx.Request().Body != nil && ctx.Request().ContentLength != 0
}

Try / catch

var p Payload
if err := ctx.ReadJSON(&p); err != nil {
    if errors.Is(err, context.ErrNotFound) {
        ctx.StatusCode(iris.StatusBadRequest)
        return errors.New("empty request body")
    }
    return err
}

Prevention

When it happens

Trigger: POST/PUT requests with Content-Length 0 or no body calling ctx.ReadJSON/ReadXML/UnmarshalBody; requests where a prior handler already consumed and closed the body.

Common situations: Clients sending POST with empty payload (e.g. fetch without body on POST); middleware that consumed the body; tests forgetting to set a request body; curl -X POST without -d.

Related errors


AI-assisted analysis of kataras/iris@7bedaf55a0 (2026-08-30). Data as JSON: /api/errors/417d5a594763a07d. Report an issue: GitHub.