OpenFeign/feign · error · HttpMessageNotReadableException

Multipart body could not be read.

Error message

Multipart body could not be read.

What it means

SpringManyMultipartFilesReader is a Spring HttpMessageConverter that parses a multipart/mixed request body into an array of ByteArrayMultipartFile. When reading any part of the multipart stream fails (malformed boundary, truncated body, I/O error), it wraps the cause in Spring's HttpMessageNotReadableException to signal the request body is unreadable.

Solutions

  1. Verify the client's Content-Type header is multipart/form-data with a boundary parameter matching the body's delimiters
  2. Compare the raw request body (e.g. via a logging filter) against the RFC 2046 multipart format the client produced
  3. Check for proxies/load balancers that truncate or re-encode request bodies
  4. If a custom encoder produced the body, ensure it writes proper CRLF-delimited boundary lines

Example fix

// before: client sends Content-Type: multipart/form-data (no boundary)
header("Content-Type", "multipart/form-data");
// after: let the form encoder set the boundary
header("Content-Type", "multipart/form-data; boundary=" + boundary);
Defensive patterns

Strategy: try-catch

Validate before calling

String ct = request.getHeaders().getFirst("Content-Type");
if (ct == null || !ct.contains("boundary=")) {
  throw new IllegalArgumentException("multipart Content-Type must carry a boundary");
}

Try / catch

try {
  MultipartFile[] parts = converter.read(type, context);
} catch (HttpMessageNotReadableException e) {
  logger.warn("malformed multipart body", e.getCause());
  throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "malformed multipart body", e);
}

Prevention

When it happens

Trigger: Calling readInternal on a request whose multipart body is malformed: wrong or missing boundary parameter, truncated stream, or readMultiPart throwing (e.g. bad Content-Disposition headers or an IOException from MultipartStream).

Common situations: A Feign/proxy client posts multipart data to a Spring controller endpoint that decodes with this converter; the client encodes with a different boundary convention, a gateway truncates the body, or the Content-Type boundary was altered in transit.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of OpenFeign/feign@e2a1e27560 (2026-09-10). Data as JSON: /api/errors/6f2df16e3cd2614f. Report an issue: GitHub.

Appendix: source

Thrown at form-spring/src/main/java/feign/form/spring/converter/SpringManyMultipartFilesReader.java:109

    MediaType contentType = headers.getContentType();
    if (contentType == null) {
      throw new HttpMessageNotReadableException("Content-Type is missing.", inputMessage);
    }

    val boundaryBytes = getMultiPartBoundary(contentType);
    MultipartStream multipartStream =
        new MultipartStream(inputMessage.getBody(), boundaryBytes, bufSize, null);

    val multiparts = new LinkedList<ByteArrayMultipartFile>();
    for (boolean nextPart = multipartStream.skipPreamble();
        nextPart;
        nextPart = multipartStream.readBoundary()) {
      ByteArrayMultipartFile multiPart;
      try {
        multiPart = readMultiPart(multipartStream);
      } catch (Exception e) {
        throw new HttpMessageNotReadableException(
            "Multipart body could not be read.", e, inputMessage);
      }
      multiparts.add(multiPart);
    }
    return multiparts.toArray(new ByteArrayMultipartFile[0]);
  }

  @Override
  protected void writeInternal(
      MultipartFile[] byteArrayMultipartFiles, HttpOutputMessage outputMessage) {
    throw new UnsupportedOperationException(
        getClass().getSimpleName() + " does not support writing to HTTP body.");
  }

  private byte[] getMultiPartBoundary(MediaType contentType) {
    val boundaryString = unquote(contentType.getParameter("boundary"));
    if (StringUtils.hasLength(boundaryString) == false) {
      throw new HttpMessageConversionException("Content-Type missing boundary information.");

View on GitHub (pinned to e2a1e27560)