Search code examples
javaspring-bootmultipartform-datacontent-type

Server Spring boot cannot parse the multipart request


Encountered an error Content-Type

There is a form on the front of the part and a file is attached to it enter image description here

I have to accept the request from the front and process it. But when the front sends a request for service, we get an error: Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'multipart/form-data;boundary=----WebKitFormBoundaryn1nNr7iOEen0Aykw' is not supported]

I might have thought that the server was not accepting requests, but I tested it via postman and everything worked well enter image description here enter image description here Front, for its part, tested requests to https://httpbin.org/#/HTTP_Methods/post_post and everything went well for them. the service sent a positive response. enter image description here

This is what it looks like js file:

async function submitDataToBackend(data, file, formDataa) {
  const formatedFormData = dataFormattingIntoJSON(data);
  const formData = new FormData();
  formData.append('form', formatedFormData);
  if (file) {
    formData.append('CV', file);
  }

  for (const pair of formData.entries()) {
    console.log('key: ', pair[0], ';       value: ', pair[1]);
  }

  const headers = new Headers();
  headers.append("Content-Type", "multipart/form-data");

  fetch('http://localhost:8081/api/v1/forms/newproject', {
    method: 'POST',
    headers: headers,
    body: formData,
  })
      .then((response) => response.json())
      .then((data) => {
        console.log('response from server: ', data);
      })
      .catch((error) => {
        console.log(error);
      });
}

Server controller that processes the request

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/forms")
@Tag(name = "Forms Controller", description = "Forms API")
@Slf4j
public class FormsController {
    private final EmailServiceMarsNet emailServiceMarsNet;

    @PostMapping(value="/newproject",consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    @Operation(summary = "Method for send email form 'Start your NEW project'")
    public StartProjectDTO sendStartProjectRequest(@RequestPart(value = "form") @Valid StartProjectDTO request,
                                                   @RequestPart(value = "CV", required = false) MultipartFile file){
        log.info(request.toString());
        emailServiceMarsNet.sendFormStartYourProjectNew(request,file);
        return request;
    }
}

maybe I wrote the controller incorrectly?

if you need more information to understand the situation, write in the comments


Solution

  • There's a basic misunderstanding I think. You put a JSON String into your FormData-Object and expect Spring to deserialize that, it's not gonna work that way. You can modify your client code to put all the fields into FormData and then deserialize it using @ModelAttribute.

    Adjustments to the Client Code:

    async function submitDataToBackend(data, file, formDataa) {
      const formData = new FormData();
      // Append each data field as a form field
      Object.keys(data).forEach(key => {
        formData.append(key, data[key]);
      });
      if (file) {
        formData.append('CV', file);
      }
    
      // Print FormData for debugging
      for (const pair of formData.entries()) {
        console.log('key: ', pair[0], ';       value: ', pair[1]);
      }
    
      fetch('http://localhost:8081/api/v1/forms/newproject', {
        method: 'POST',
        body: formData, // No headers field
      })
      .then((response) => response.json())
      .then((data) => {
        console.log('response from server: ', data);
      })
      .catch((error) => {
        console.log(error);
      });
    }
    

    Adjustments to the Controller:

    public StartProjectDTO sendStartProjectRequest(@ModelAttribute StartProjectDTO request,
                                                       @RequestParam(value = "CV", required = false) MultipartFile file) {
    

    Reference:

    https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.html

    A different approach you could do is to change the expected type in your controller to String and deserialize it yourself using ObjectMapper.

    If you want to go that way, which I would not recommend this would be the code:

    @RestController
    @RequiredArgsConstructor
    @RequestMapping("/api/v1/forms")
    @Tag(name = "Forms Controller", description = "Forms API")
    @Slf4j
    public class FormsController {
        private final EmailServiceMarsNet emailServiceMarsNet;
        private final ObjectMapper objectMapper;
    
        @PostMapping(value="/newproject",consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
        @Operation(summary = "Method for send email form 'Start your NEW project'")
        public StartProjectDTO sendStartProjectRequest(@RequestPart(value = "form") String request,
                                                       @RequestPart(value = "CV", required = false) MultipartFile file){
            log.info(request.toString());
            StartProjectDTO startProjectDTO = objectMapper.readValue(request, StartProjectDTO.class);
            emailServiceMarsNet.sendFormStartYourProjectNew(request,file);
            return request;
        }
    }
    

    Also one thing to mention, since you are using Swagger you could go code-first approach for an OpenAPI Spec and generate the client-code to call your API automatically.

    https://github.com/OpenAPITools/openapi-generator