4

when I post request to this server code - everething works good:

@RequestMapping(method = RequestMethod.POST, consumes = { "application/json" })
public ResponseEntity<String> addQuestion(@RequestBody String dtoObject) { ... }

but if I change request to "multipart/form-data" - Spring returns error 400 "Bad request":

@RequestMapping(method = RequestMethod.POST, consumes = { "multipart/form-data" })
public ResponseEntity<String> addQuestion(@RequestBody String dtoObject) { ... }

Why? May be I should create some extra bean?

PS: I need "multipart/form-data" for sending files together with json objects.

Maxim
  • 176
  • 1
  • 3
  • 10
  • 2
    if you are using a tool like Postman to test your REST service routes, in that tool you will have to assign the `Content-type`header key as `multipart/form-data` – Mustapha Belmokhtar Oct 01 '17 at 22:47
  • 1
    in Postman I get the same result (error 400 "Bad request") – Maxim Oct 02 '17 at 09:56
  • please refer to your console and read the whole stack trace of this exception, there you may know where this error comes from. – Mustapha Belmokhtar Oct 02 '17 at 10:26
  • mustabelMo: sorry, may be I don't understand you: error 400 "Bad request" is not the exception, it is the code of server response, so I don't have the stacktrace for it. – Maxim Oct 02 '17 at 11:20
  • Usually when an exception is occured, we can see the log of the stackTrace on the IDE console – Mustapha Belmokhtar Oct 02 '17 at 11:30

2 Answers2

6

I think you can't deserialize the file to that dtoObject within the request body. you will need to use @RequestPart to do that.

@RequestMapping(method = RequestMethod.POST, consumes = { "multipart/form-data" })
public ResponseEntity<String> addQuestion2(@RequestPart("question") QuestionPostDto dtoObject, @RequestPart("file") MultiPartFile file)  { ... }

your request need to be formdata: with the file you want to upload and json format file question.json

here is my payload example from post man

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="QLbLFIR.gif"
Content-Type: image/gif


------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="question"; filename="question.json"
Content-Type: application/json


------WebKitFormBoundary7MA4YWxkTrZu0gW--

or if you don't want to pass a json format file you can pass it with a normal string

@RequestMapping(method = RequestMethod.POST, consumes = { "multipart/form-data" })
    public ResponseEntity<String> addQuestion2(String question, @RequestPart("file") MultiPartFile file)  {
    QuestionPostDto dtoObject = new ObjectMapper().readValue(request, QuestionPostDto.class); 
    // do sth
}

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="QLbLFIR.gif"
Content-Type: image/gif


------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="request"

{
    "key": "value"
}
------WebKitFormBoundary7MA4YWxkTrZu0gW--

see this thread for more detail: Spring MVC Multipart Request with JSON

Chi Dov
  • 1,447
  • 1
  • 11
  • 22
  • if I try to post String - I get error 400 "Bad request" (edited the question). @RequestPart - works with String, but does not work with my custom type QuestionPostDto (Spring returns error 415 "Unsupported Media Type"). – Maxim Oct 02 '17 at 09:51
  • are you using postman to test your request, if so can I see your request payload? – Chi Dov Oct 02 '17 at 15:38
  • Chi Dov, thank you for information. You was right - I had to use @RequestPart. See my answer below. – Maxim Oct 02 '17 at 20:35
1

I solved this problem.

1) I created the HttpMessageConverter, wich converts json to my custom type QuestionPostDto:

public class QuestionPostDtoHttpMessageConverter implements HttpMessageConverter<QuestionPostDto> {

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return QuestionPostDto.class == clazz;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return QuestionPostDto.class == clazz;
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        List<MediaType> list = new ArrayList<MediaType>();
        list.add(MediaType.MULTIPART_FORM_DATA);
        return list;
    }

    @Override
    public QuestionPostDto read(Class<? extends QuestionPostDto> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        InputStream istream = inputMessage.getBody();
        String requestString = IOUtils.toString(istream, "UTF-8");

        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(requestString, QuestionPostDto.class);
    }

    @Override
    public void write(QuestionPostDto t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
    }
}

2) I create bean for this type of HttpMessageConverter (we use Spring Boot in project):

@Configuration
public class HttpConfiguration {
...
    @Bean
    public QuestionPostDtoHttpMessageConverter commonsMultipartResolver() {
        return new QuestionPostDtoHttpMessageConverter();
    }
}

3) Now my code in RestController works fine:

@RestController
@RequestMapping("/api/question")
@ConfigurationProperties(prefix = "question")
@RequiredArgsConstructor
@Slf4j
public class QuestionController {
...
@PostMapping
ResponseEntity<String> addQuestion(@RequestPart("dtoObject") QuestionPostDto dtoObject, @RequestPart("file") MultipartFile file) { ... }

Thanks to everybody, especially Chi Dov

Maxim
  • 176
  • 1
  • 3
  • 10