Main Tutorials

Spring Boot file upload example – Ajax and REST

This article shows you how to upload files in Spring Boot web application (REST structure), using Ajax requests.

Tools used in this article :

  1. Spring Boot 1.4.3.RELEASE
  2. Spring 4.3.5.RELEASE
  3. Thymeleaf
  4. jQuery (webjars)
  5. Maven
  6. Embedded Tomcat 8.5.6
  7. Google Chrome Browser (Network Inspect)

1. Project Structure

A standard Maven project structure.

spring-boot-file-upload-ajax-directory-1

2. Project Dependency

Declares an extra jQuery webjar dependency, for Ajax requests in HTML form.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mkyong</groupId>
    <artifactId>spring-boot-file-upload</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.3.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- hot swapping, disable cache for template, enable live reload -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>2.2.4</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- Package as an executable jar/war -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3. File Upload

To support Ajax request and response, the easiest solution is returned a ResponseEntity.

3.1 The below example demonstrates three possible ways to upload files:

  1. Single file upload – MultipartFile
  2. Multiple file upload – MultipartFile[]
  3. Map file upload to a Model – @ModelAttribute
RestUploadController.java

package com.mkyong.controller;

import com.mkyong.model.UploadModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@RestController
public class RestUploadController {

    private final Logger logger = LoggerFactory.getLogger(RestUploadController.class);

    //Save the uploaded file to this folder
    private static String UPLOADED_FOLDER = "F://temp//";

    // 3.1.1 Single file upload
    @PostMapping("/api/upload")
    // If not @RestController, uncomment this
    //@ResponseBody
    public ResponseEntity<?> uploadFile(
            @RequestParam("file") MultipartFile uploadfile) {

        logger.debug("Single file upload!");

        if (uploadfile.isEmpty()) {
            return new ResponseEntity("please select a file!", HttpStatus.OK);
        }

        try {

            saveUploadedFiles(Arrays.asList(uploadfile));

        } catch (IOException e) {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }

        return new ResponseEntity("Successfully uploaded - " +
                uploadfile.getOriginalFilename(), new HttpHeaders(), HttpStatus.OK);

    }

    // 3.1.2 Multiple file upload
    @PostMapping("/api/upload/multi")
    public ResponseEntity<?> uploadFileMulti(
            @RequestParam("extraField") String extraField,
            @RequestParam("files") MultipartFile[] uploadfiles) {

        logger.debug("Multiple file upload!");

        // Get file name
        String uploadedFileName = Arrays.stream(uploadfiles).map(x -> x.getOriginalFilename())
                .filter(x -> !StringUtils.isEmpty(x)).collect(Collectors.joining(" , "));

        if (StringUtils.isEmpty(uploadedFileName)) {
            return new ResponseEntity("please select a file!", HttpStatus.OK);
        }

        try {

            saveUploadedFiles(Arrays.asList(uploadfiles));

        } catch (IOException e) {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }

        return new ResponseEntity("Successfully uploaded - "
                + uploadedFileName, HttpStatus.OK);

    }

    // 3.1.3 maps html form to a Model
    @PostMapping("/api/upload/multi/model")
    public ResponseEntity<?> multiUploadFileModel(@ModelAttribute UploadModel model) {

        logger.debug("Multiple file upload! With UploadModel");

        try {

            saveUploadedFiles(Arrays.asList(model.getFiles()));

        } catch (IOException e) {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }

        return new ResponseEntity("Successfully uploaded!", HttpStatus.OK);

    }

    //save file
    private void saveUploadedFiles(List<MultipartFile> files) throws IOException {

        for (MultipartFile file : files) {

            if (file.isEmpty()) {
                continue; //next pls
            }

            byte[] bytes = file.getBytes();
            Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());
            Files.write(path, bytes);

        }

    }
}

3.2 A simple model for above example 3.1.3 – @ModelAttribute

UploadModel.java

package com.mkyong.model;

import org.springframework.web.multipart.MultipartFile;

public class UploadModel {

    private String extraField;

    private MultipartFile[] files;

    //getters and setters
}

4. The Views

HTML form for multiple file uploads.

upload.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>

<h1>Spring Boot - Multiple file upload example - AJAX</h1>

<form method="POST" enctype="multipart/form-data" id="fileUploadForm">
    <input type="text" name="extraField"/><br/><br/>
    <input type="file" name="files"/><br/><br/>
    <input type="file" name="files"/><br/><br/>
    <input type="submit" value="Submit" id="btnSubmit"/>
</form>

<h1>Ajax Post Result</h1>
&lt;pre&gt;
    <span id="result"></span>
&lt;/pre&gt;

<script type="text/javascript"
        src="webjars/jquery/2.2.4/jquery.min.js"></script>

<script type="text/javascript" src="js/main.js"></script>

</body>
</html>

5. jQuery – Ajax Request

jQuery to get the form via form #id, and send the multipart form data via Ajax request.

resources/static/js/main.js

$(document).ready(function () {

    $("#btnSubmit").click(function (event) {

        //stop submit the form, we will post it manually.
        event.preventDefault();

        fire_ajax_submit();

    });

});

function fire_ajax_submit() {

    // Get form
    var form = $('#fileUploadForm')[0];

    var data = new FormData(form);

    data.append("CustomField", "This is some extra data, testing");

    $("#btnSubmit").prop("disabled", true);

    $.ajax({
        type: "POST",
        enctype: 'multipart/form-data',
        url: "/api/upload/multi",
        data: data,
        //http://api.jquery.com/jQuery.ajax/
        //https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects
        processData: false, //prevent jQuery from automatically transforming the data into a query string
        contentType: false,
        cache: false,
        timeout: 600000,
        success: function (data) {

            $("#result").text(data);
            console.log("SUCCESS : ", data);
            $("#btnSubmit").prop("disabled", false);

        },
        error: function (e) {

            $("#result").text(e.responseText);
            console.log("ERROR : ", e);
            $("#btnSubmit").prop("disabled", false);

        }
    });

}

6. Exception Handler

To handle exception from Ajax request, just extends ResponseEntityExceptionHandler and return back a ResponseEntity.

RestGlobalExceptionHandler.java

package com.mkyong.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletRequest;

//http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling
@ControllerAdvice
public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler {

    // Catch file size exceeded exception!
    @ExceptionHandler(MultipartException.class)
    @ResponseBody
    ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {

        HttpStatus status = getStatus(request);
        return new ResponseEntity(ex.getMessage(), status);

        // example
        //return new ResponseEntity("success", responseHeaders, HttpStatus.OK);

    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        return HttpStatus.valueOf(statusCode);
    }

}

7. DEMO

Start Spring Boot with the default embedded Tomcat mvn spring-boot:run.

7.1 Access http://localhost:8080/, select few files and clicks submit to fire the ajax request.

spring-boot-file-upload-ajax-1

7.2 Google Chrome, review the request and response in the “Network Inspect”

spring-boot-file-upload-ajax-2

7.3 Google Chrome, “Request Payload”

spring-boot-file-upload-ajax-3

8. cURL Testing

More testing with cURL command.

8.1 Test single file upload.

Terminal

$ curl -F file=@"f:\\data.txt" http://localhost:8080/api/upload/
Successfully uploaded - data.txt

8.2 Test multiple file upload.

Terminal

$ curl -F extraField="abc" -F files=@"f://data.txt" -F files=@"f://data2.txt"  http://localhost:8080/api/upload/multi/
Successfully uploaded - data.txt , data2.txt

8.3 Test multiple file upload, maps to Model.

Terminal

$ curl -F extraField="abc" -F files=@"f://data.txt" -F files=@"f://data2.txt"  http://localhost:8080/api/upload/multi/model
Successfully uploaded!

8.4 Test a large movie file (100MB), the following error message will be displayed.

Terminal

$ curl -F file=@"F://movies//300//Sample.mkv"  http://localhost:8080/api/upload/
Attachment size exceeds the allowable limit! (10MB)

9. cURL Testing + Custom Error object

9.1 Create an object to store the error detail.

CustomError.java

package com.mkyong.exception;

public class CustomError {

    String errCode;
    String errDesc;

    public CustomError(String errCode, String errDesc) {
        this.errCode = errCode;
        this.errDesc = errDesc;
    }

    //getters and setters
}

9.2 Update the global exception handler to support CustomError object.

RestGlobalExceptionHandler.java

package com.mkyong.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletRequest;

@ControllerAdvice
public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(MultipartException.class)
    @ResponseBody
    ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {

        HttpStatus status = getStatus(request);

        return new ResponseEntity(new CustomError("0x000123", 
                "Attachment size exceeds the allowable limit! (10MB)"), status);

        //return new ResponseEntity("Attachment size exceeds the allowable limit! (10MB)", status);

    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        return HttpStatus.valueOf(statusCode);
    }

}

9.3 cURL to upload a large file again.

Terminal

$ curl -F file=@"F://movies//300//Sample.mkv"  http://localhost:8080/api/upload/

{"errCode":"0x000123","errDesc":"Attachment size exceeds the allowable limit! (10MB)"}

Done. Feedback is welcome.

10. Download Source Code

References

  1. Spring Boot – Error Handling
  2. Spring Boot file upload example
  3. Spring MVC file upload example
  4. Spring Boot Hello World Example – Thymeleaf
  5. Wikipedia – cURL
  6. jQuery.ajax()
  7. MDN – Using FormData Objects

About Author

author image
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

Subscribe
Notify of
27 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Diego André Colli
6 years ago

I don’t know why I receive: “Required request part ‘file’ is not present” Anyty help?

chencong-plan
5 years ago

i guess you dont have “name = ‘’file‘ ’”

Ranju
5 years ago

I am also facing the same issue. I am getting files as null.

konda
4 years ago
Reply to  Ranju

please set file path to ur local drive

sonal k
4 years ago

FileUploadException: the request was rejected because no multipart boundary was found
when trying from postman

PraBhu
4 years ago

Hi mkyong, I am a big fan.
I feel <input type="file" name="files"/> should be changed to <input type="file" name="file"/> for this code to work.

karthik reddy2323
4 years ago

how to developt rest client for above api?

kirkiri
4 years ago

Could you give example for rest client for above rest api?

raj2424
4 years ago

Could you show rest client code for above api call?

samuel jawahar
5 years ago

how do you mapped the files in static and template folders

Biran
5 years ago

Hello, guys how do i upload file and student info object with ajax on spring boot ?? I need a Help guys

dka
6 years ago

Hi @mkyong, I have the same error as Diego André Colli.
I’ve posted my configuration here : https://stackoverflow.com/questions/47737003/spring-fileupload-required-request-part-file-is-not-present

mqw
6 years ago

spingboot + angularjs is really cool .. don’t you like it?

Sanjeev
6 years ago

How to upload multiple files with a single file selection meaning assume that I have and I am trying to upload multiple files together along with some extra data in that case how to process the uploading of files

Venkat
6 years ago

Hi ,
I am getting this error.Could any one check this.
My Error :

{“code”:500,”description”:”Could not write content: No serializer found for class java.io.ByteArrayInputStream and
no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
(through reference chain: com.test.sample.CountryBean[“files”]->java.util.ArrayList[0]->org
.springframework.mock.web.MockMultipartFile[“inputStream”]); nested exception is com.fasterxml.jackson.databind.
JsonMappingException: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to
create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
(through reference chain: com.test.sample.CountryBean[“files”]
->java.util.ArrayList[0]->org.springframework.mock.web.MockMultipartFile[“inputStream”])”}

Venkat
6 years ago

Hi Mkyong,

Thanks for the sharing the project on MultipartFile usage.I am stuckup with the retrieval process on this.My bean is look like below.

Class CountryBean{
private Country;
private List filesList;
//setters and getters
}

Class Country{
String countryName;
int population;
//setters and getters
}
I have to return the CountryBean in Controller for complete Country bean data and Files for updation process in UI.

Could you show any example on this.

Venkat
6 years ago
Reply to  Venkat

My Error :

{“code”:500,”description”:”Could not write content: No serializer found for class java.io.ByteArrayInputStream and
no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
(through reference chain: com.test.sample.CountryBean[“files”]->java.util.ArrayList[0]->org
.springframework.mock.web.MockMultipartFile[“inputStream”]); nested exception is com.fasterxml.jackson.databind.
JsonMappingException: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to
create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
(through reference chain: com.test.sample.CountryBean[“files”]
->java.util.ArrayList[0]->org.springframework.mock.web.MockMultipartFile[“inputStream”])”}

My Controller is like below :

@GetMapping(value=”/country/{id}”, produces=”application/json”)
public ResponseEntity getCountryDetails(@PathVariable(“id”)Integer id) {
log.debug(” Start getCountryDetails “);
CountryBean countryModel = null;
try{
countryModel = countryService.getInvestmentRequestById(id);
log.info(” :- Files Count -:::- “+countryModel.getFiles().size());

if(countryModel.getCountryId()==id){
return new ResponseEntity(countryModel,HttpStatus.OK);
}else if(countryModel.getCountryId()!=id){
return new ResponseEntity(countryModel,HttpStatus.NOT_FOUND);
}else{
return new ResponseEntity(countryModel,HttpStatus.BAD_REQUEST);
}
}catch(Exception ex){
log.error(ex.getMessage());
}

log.debug(” End getCountryDetails “);
return new ResponseEntity(countryModel,HttpStatus.BAD_REQUEST);
}

Venkat
6 years ago
Reply to  Venkat

Class CountryBean{
private Country;
private List filesList;
//setters and getters
}

Sorry I mistyped.

roky
6 years ago

Hi Mkyong
i have this error
{“timestamp”:1494517860072,”status”:500,”error”:”Internal Server Error”,”exception”:”java.nio.file.InvalidPathException”,”message”:”Illegal char at index 7: /temp/D:\spring-boot-file-upload-ajax-rest\pom.xml”,”path”:”/api/upload/multi”}
thank you

Kehinde Adetiloye
6 years ago

This does not work when I included authentication, is there something I am missing that I need to add when I include authentication (jwt) that would make it work?

Palu Gagson
7 years ago

Hello, I have problem with this solution. When i try to upload bigger file than 10mb with curl (curl -F file=@”C://path//to//file” http://localhost:8080/api/upload/) I get error curl: (56) Recv failure: Connection was aborted

mkyong
7 years ago
Reply to  Palu Gagson

This example is limited the uploaded file size to 10mb, try increase it.

b singh
5 years ago
Reply to  mkyong

Thanks for the example. How would you write a spring boot validator for request param MultipartFile[] in uploadFileMulti method. It would be difficult to pass MultipartFile[].
Regards,
B

Palu Gagson
7 years ago
Reply to  Palu Gagson

I was using portable curl.exe (dont know if that was problem) everything I needed to do was uncomment bean located in SpringBootWebApplication. Application is working great when using browser but curl is showing same error

Opencodez
7 years ago

Very good article and example mkyong.

ASDF
6 years ago

good article

Dharshan
6 years ago

You awesome …….. you make me a happy sleep