Search code examples
springspring-bootpostmultipartform-dataresttemplate

Spring Boot: post and process multipart/form-data request with multiple body parts


I have the following endpoint that processes the multipart/form-data requests:

    @RequestMapping(value = "/test", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<Response> handleRequest(
            @RequestPart(value = "image") MultipartFile image,
            @RequestPart(value = "data") Request request, HttpServletRequest request) throws IOException {
    }

When I test it with IDEA rest client the endpoint is being reached as expected:

POST http://myUrl/test
Accept: */*
Cache-Control: no-cache
Content-Type: multipart/form-data; boundary=xxxx
Authorization: Basic username pass

--xxxx
Content-Disposition: form-data; name="image"; filename="image.jpg"
Content-Type: image/jpg

< C:\pathToImage\image.jpg
--xxxx
Content-Disposition: form-data; name="data"
Content-Type: application/json

{
"firstName":"John",
"lastName":"Doe"
}
--xxxx

But when I am trying to reach this endpoint via Spring RestTemplate, I observe the 400 response and the following error: Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'image' is not present].

The code I am using for posting the request is the following:

public Response test(final byte[] image, MediaType imageMimeType, final Request request) {
        String url = "http://myUrl/test";
        HttpHeaders headers = HeaderUtils.headerBasicAuth("username", "pass");
        headers.set(HttpHeaders.ACCEPT,MediaType.APPLICATION_JSON_VALUE);
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
        map.add("image", new InputStreamResource(new ByteArrayInputStream(image)));
        map.add("data", request.toJson());
        HttpEntity<?> requestEntity = new HttpEntity<>(map, headers);
        ResponseEntity<Response> result = template.exchange(url, HttpMethod.POST, requestEntity, Response.class);
        return result.getBody();
    }

The approximate code of the Request class is below:

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;
import java.io.Serializable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.json.JSONObject;

@AllArgsConstructor(access = AccessLevel.PUBLIC)
@Getter
@JsonInclude(Include.NON_NULL)
public final class VerifyRequest implements Cloneable, Serializable {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private final String firstName;
    private final String lastName;

    public JSONObject toJson() {
        String json;
        try {
            json = OBJECT_MAPPER.writeValueAsString(this);
        } catch (JsonProcessingException var3) {
            throw new IllegalStateException(var3);
        }

        return new JSONObject(json);
    }
    ....
}

Any suggestions how could I post such kind of request?


Solution

  • You get that error because your are not using a valid object in the MultiValueMap for the image field. Normally you would use FileSystemResource to load the image from the disk, there are many examples of that.

    However if you still want to get it from a byte array you could use a ByteArrayResource overriding getFilename() which is necessary on the controller side. So instead of:

    map.add("image", new InputStreamResource(new ByteArrayInputStream(image)));

    just put:

    ByteArrayResource byteArrayResource = new ByteArrayResource(image) {
        @Override
        public String getFilename() {
            return "image.jpg";
        }
    };
    map.add("image", byteArrayResource);