Search code examples
angulartypescriptspring-bootmultipartform-data

Angular file upload to Spring Boot fails with multipart boundary rejection


I am trying to upload Angular form data to Spring Boot server.

Simply, in Spring Boot I accept:

data class Photo(
    val id: Long = 0L,
    val file: MultipartFile
)

data class PostRequest(
    @field:Size(min = 1, max = 100)
    val title: String,

    val photos: List<Photo> = mutableListOf()
)

via Controller:

fun createPost(@Valid @ModelAttribute postRequest: PostRequest)

In Angular I created this form:

this.postForm = this.fb.group({
   title: ["", [Validators.required, Validators.minLength(1)]],
   photos: this.fb.array([]),
});

When user uploads a photo I call these lines:

const photosArray = this.postForm.get("photos") as FormArray;
          
const newPhotoGroup = this.fb.group({
  id: "",
  file: event.target.result,
});
          
photosArray.push(newPhotoGroup);

Finally, I construct a form data object:

const formData = new FormData();
formData.append("title", this.postForm.get("title")?.value);
formData.append("photos", this.postForm.get("photos")?.value);

I use this function to post this form to API:

 postRequest<T>(url: string, body: any): Observable<HttpResponse<T>> {
    const headers = new HttpHeaders({
      "Content-Type": "multipart/form-data",
    });

    return this.http.post<T>(url, body, {
      headers,
      observe: "response",
      withCredentials: true,
    });
  }

In developer tools I see this request:

enter image description here

Spring Boot complains:

org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found

Someone, enlighten me. Thank you.


Solution

  • photos form control is an array of objects, which is sent like that (as [object Object]) in the request, hence the error.

    You need to loop the array and append each file:

    const formData = new FormData();
    formData.append("title", this.postForm.get("title")?.value);
    // loop files and append them
    this.postForm.get("photos")?.value.forEach((obj:any, i:number)=>{
        formData.append("photos", obj.file); // user property which has file:File
    });
    

    Also, when adding file to the array, make sure it has File type (event.target.result seems to be a string now, if so, use event.target.file or add a new property, for example 'uploadFile', and add that to formData later):

    const newPhotoGroup = this.fb.group({
        id: "",
        file: event.target.result, // this should be File from event.target.file
      });
    

    if you want to include id, i.e. custom file data, along with the file, it cannot go along with the file in the same formData key

    You could use id instead of filename:

    formData.append("photos", obj.file, obj.id)
    

    or adapt the upload process something like this: sending JSON object along with file using FormData in ajax call and accessing the json object in PHP


    Finally, make the reqeuest without content-type headers, the FormData will handle it:

    return this.http.post<T>(url, body, {
        observe: "response",
        withCredentials: true,
      });