SpringBoot Upload Download Multiple Files with Rest API + Ajax Tutorial

SpringBoot upload download multiple files with RestAPI and Ajax

How to build SpringBoot RestAPIs and Ajax client to Upload Download Multiple Files in one of the most common question in the development world? So in the tutorial, I will guide step by step how to do it by examples with clearly and running sourcecode.

Related posts:

  1. Spring Boot Security JWT Authentication Example – MySQL/PostgreSQL + Spring JPA + RestAPIs
  2. Angular Spring Boot JWT Authentication Example – Angular 6, 8, 9 + Spring Security + MySQL/PostgreSQL
  3. SpringBoot RestAPIs Upload Download Multiple CSV files to MySQL/PostgreSQL with Ajax + RestClient

What will we do in the post?

Now let’s go!

Overview

We create a SpringBoot RestAPIs project to Upload and Download single/multiple files with 2 main block:

  • Backend block: implements Upload/Download RestAPI controllers that uses File Service to store or retrieve files from file system then returns back a http response message to Ajax Rest Client
  • Frontend block: creates an Ajax Rest Client to upload and download single and multiple files from the SpringBoot RestAPIs
SpringBoot Upload Download Multiple Files RestAPI and Ajax Overview
SpringBoot Upload Download Multiple Files RestAPI and Ajax Overview

We create a SpringBoot project with below structure to implement about design:

SpringBoot Upload Download Multiple Files RestAPI and Ajax project structure
SpringBoot Upload Download Multiple Files RestAPI and Ajax project structure
  • controller package implements Upload and Download RestAPIs controllers
  • service package implements specific functions to store and retrieve file systems that will be used by Upload and Download Controllers
  • message package defines a needed information in a response object to return back to Ajax Rest Client.
  • errorhandler package implements a global restapi exception handler.
  • index.html html file defines upload and download views.
  • uploaddownloadfiles.js implements Ajax functions to upload and download files from SpringBoot RestAPIs.

For the tutorial, we use Eclipse integrated with SpringToolSuite to create a SpringBoot project with a spring-boot-starter-web dependency:

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

Implement File Service

We define a FileService interface to interact (save/retrieve files) with File System. Here is the details sourcecode:

package com.loizenjava.uploaddownloadfiles.service;

import java.nio.file.Path;
import java.util.stream.Stream;

import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;


/**
 * Copyright by https://loizenjava.com
 * @author loizenjava.com
 *
 */

public interface FileService {
	public void store(MultipartFile file);

	public Resource loadFile(String filename);

	public void deleteAll();

	public void init();

	public Stream<Path> getFiles();
}
  • store(MultipartFile file) is used to save a file to disk
  • Resource loadFile(String filename) is used to retrieve a file from disk
  • voide deleteAll() is used to delete all file in a upload folder.
  • void init() is used to create a upload folder

We implement above File Service APIs in FileServiceImpl.java class:

package com.loizenjava.uploaddownloadfiles.service;

import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.multipart.MultipartFile;

/**
 * Copyright by https://loizenjava.com
 * @author loizenjava.com
 *
 */

@Service
public class FileServiceImpl implements FileService {

	Logger log = LoggerFactory.getLogger(this.getClass().getName());
	private final Path rootLocation = Paths.get("storages");

	@Override
	public void store(MultipartFile file) {
		try {
			Files.copy(file.getInputStream(), this.rootLocation.resolve(file.getOriginalFilename()));
		} catch (Exception e) {
			throw new RuntimeException("Error! -> message = " + e.getMessage());
		}
	}

	@Override
	public Resource loadFile(String filename) {
		try {
			Path file = rootLocation.resolve(filename);
			Resource resource = new UrlResource(file.toUri());
			if (resource.exists() || resource.isReadable()) {
				return resource;
			} else {
				throw new RuntimeException("FAIL!");
			}
		} catch (MalformedURLException e) {
			throw new RuntimeException("Error! -> message = " + e.getMessage());
		}
	}

	@Override
	public void deleteAll() {
		FileSystemUtils.deleteRecursively(rootLocation.toFile());
	}

	@Override
	public void init() {
		try {
			Files.createDirectory(rootLocation);
		} catch (IOException e) {
			throw new RuntimeException("Could not initialize file storage!");
		}
	}

	@Override
	public Stream<Path> getFiles() {
		try {
			return Files.walk(this.rootLocation, 1).filter(path -> !path.equals(this.rootLocation))
					.map(this.rootLocation::relativize);
		} catch (IOException e) {
			throw new RuntimeException("Error! -> message = " + e.getMessage());
		}
	}
}

Design Response Message

I define a Response class to contain necessary information to return back to rest Api client. In the tutorial, I define a Response class with 4 attributes:

  • messages attribute contains a list information of upload proccessing of each file (ok or fail)
  • fileInfos attribute contains a list information of each uploaded files.
  • error attribute contains detail information of an error if having any exception which catched by Globle RestAPI Exceptions when do uploading files.
public class Response {
	private List<Message> messages = null;
	private List<FileInfo> fileInfos = null;
	private Error error = null;
	private String errStatus = "";
	
	public Response() {
		this.messages = new ArrayList<Message>();
	}
	
	public Response(List<FileInfo> fileInfos) {
		this.fileInfos = fileInfos; 
	}
	
	public Response(String errStatus, Error err) {
		this.errStatus = errStatus;
		this.error = err;
	}
        //...

Implement Upload RestAPI Controller

We define a UploadController.java class to provide uploading RestAPIs with 2 specific usercases:

  • Upload a single file restapi
  • Upload multiple files restapi
Spring Upload Single Multiple Controller RestAPIs
Spring Upload Single Multiple Controller RestAPIs

Upload Single File RestAPIs

In the UploadController.java class, we define a method uploadSingleFile(@RequestParam("uploadfile") MultipartFile uploadfile) to handle any http POST request with URL /api/upload/file/single for uploading a single file:

@PostMapping("/single")
public Response uploadSingleFile(@RequestParam("uploadfile") MultipartFile uploadfile) {
	
	Response response = new Response();

	// Checking the upload-file before processing 
	if(uploadfile.getOriginalFilename().isEmpty()) {
		response.addMessage(new Message(uploadfile.getOriginalFilename(), 
							"No selected file to upload! Please do the checking", "fail"));
		
		return response;
	}
	
	try {
		// save file to disk
		fileService.store(uploadfile);
		response.addMessage(new Message(uploadfile.getOriginalFilename(), "Upload Successfully!", "ok"));
	} catch(Exception e) {
		response.addMessage(new Message(uploadfile.getOriginalFilename(), e.getMessage(), "fail"));
	}
	
	return response;
}

Upload Multiple Files RestAPIs

In the UploadController.java class, we define a method uploadMultipleFiles(@RequestParam("uploadfiles") MultipartFile[] uploadfiles) to handle any http POST request to URL /api/upload/file/multiple for uploading multiple files. Here is a detail:


@PostMapping("/multiple")
public Response uploadMultipleFiles(@RequestParam("uploadfiles") MultipartFile[] uploadfiles) {
	
	Response response = new Response();
	/*
	 * Filtering files had been selected for uploading (the files having names)
	 */
	MultipartFile[] readyUploadedFiles = Arrays.stream(uploadfiles).filter(
								x -> !StringUtils.isEmpty(x.getOriginalFilename())).toArray(MultipartFile[]::new);

    /*
     * Checking whether having at least one file had been selected for uploading
     */
    if (readyUploadedFiles.length == 0) {
    	response.addMessage(new Message("", "No selected file to upload!", "fail"));
    	return response;
    }

    /*
     * Do the uploading
     */
	for (MultipartFile file : readyUploadedFiles) {
		try {
			fileService.store(file);
			response.addMessage(new Message(file.getOriginalFilename(), "Upload Successfully!", "ok"));
		} catch (Exception e) {
			response.addMessage(new Message(file.getOriginalFilename(), e.getMessage(), "fail"));
		}
	}
	
	return response;
}

Implement Download Controller RestAPI

We create a class DownloadController.java to download uploaded files with 2 methods:

  • getListFiles(Model model) is used to list all uploaded files' name
  • downloadFile(@PathVariable String filename) is used to download a file with a specific name
SpringBoot Download Files methods in DownloadController
SpringBoot Download Files methods in DownloadController
package com.loizenjava.uploaddownloadfiles.controller;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;

import com.loizenjava.uploaddownloadfiles.message.FileInfo;
import com.loizenjava.uploaddownloadfiles.message.Response;
import com.loizenjava.uploaddownloadfiles.service.FileService;

@RestController
@RequestMapping("/api/download")
public class DownloadController {

	@Autowired
	FileService fileService;

	/*
	 * Retrieve Files' Information
	 */
	@GetMapping("/files")
	public Response getListFiles(Model model) {

		List<FileInfo> files = fileService.getFiles().map(path -> {
			String filename = path.getFileName().toString();
			String url = MvcUriComponentsBuilder
					.fromMethodName(DownloadController.class, "downloadFile", path.getFileName().toString()).build()
					.toString();

			return new FileInfo(filename, url);
		}).collect(Collectors.toList());

		return new Response(files);
	}

	/*
	 * Download Files
	 */
	@GetMapping("/file/{filename}")
	public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
		
		MediaType contentType = MediaType.IMAGE_PNG;
		
		String extension = filename.split("\\.")[1];
		
		switch(extension) {
			case "txt": contentType = MediaType.TEXT_PLAIN;
						break;
			case "xlsx": contentType = MediaType.parseMediaType("application/vnd.ms-excel");
						break;
		}
		
		
		HttpHeaders headers = new HttpHeaders();
		headers.add("Content-Disposition", "attachment; filename=" + filename);
		
		return ResponseEntity.ok().headers(headers)
									.contentType(contentType)
									.body(fileService.loadFile(filename));
	}
}

Implement Global Exception Handler

If an upload file has an enough big size, Springboot application will throw an exception. How to handle the exception? We need to define an RestExceptionHandler class that extends ResponseEntityExceptionHandler and use an annotation @ControllerAdvice to handle the big size issue.

package com.loizenjava.uploaddownloadfiles.errorhandler;

import javax.servlet.http.HttpServletRequest;

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 com.loizenjava.uploaddownloadfiles.message.Error;
import com.loizenjava.uploaddownloadfiles.message.Response;

@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

    // Catch file size exceeded exception!
    @SuppressWarnings({ "rawtypes", "unchecked" })
	@ExceptionHandler(MultipartException.class)
    @ResponseBody
    ResponseEntity<Response> handleControllerException(HttpServletRequest request, Throwable ex) {
        HttpStatus status = getStatus(request);
        return new ResponseEntity(new Response("error", new Error("0x123", ex.getMessage())), 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);
    }
}

Test RestAPIs

We define a scenario with 5 testcases to test the implemented RestAPIs:

Upload single file testcase

Make a http POST request to URL http://localhost:8080/api/upload/file/single to upload a single file:

Test case upload single file request
Test case upload single file request

Reponse:

Test case upload single file response - successfully
Test case upload single file response - successfully

Upload multiple files test case

Make a http POST request to URL http://localhost:8080/api/upload/file/multiple to upload multiple files:

Test case upload multiple files request
Test case upload multiple files request

Response:

Testcase upload multiple files response - successfully
Testcase upload multiple files response - successfully

Get all uploaded files' name testcase

Make a http GET request to URL http://localhost:8080/api/download/files to get all uploaded files' names

Testcase get all uploaded files' names
Testcase get all uploaded files' names

Response:

Test case get all uploaded files' name response - successfully
Test case get all uploaded files' name response - successfully

Download a file with a specific name

Make a http GET request to URL http://localhost:8080/api/download/file/loizenjava-tutorial.txt to download a uploaded file with a specific name loizenjava-tutorial.txt:

Testcase get a uploaded file with a specific name
Testcase get a uploaded file with a specific name

Response - Successfully:

Testcase get a uploaded file with a specific name - response successfully
Testcase get a uploaded file with a specific name - response successfully

Create Upload Download Html Views

We use Bootstrap framework to define a Upload Download Html View with 3 separated parts:

  • a form to upload single file
  • a form to upload multiple files
  • a div to get all uploaded files
upload download view
upload download view
<!DOCTYPE html>
<html lang="en">
<head>
<title>Upload Download File Examples</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/js/uploaddownloadfiles.js"></script>
</head>
<body>
	<div class="container">
		<div class="row">
			<div class="col-sm-7" style="background-color:#e6fffa; padding:10px; border-radius:3px">
				<h3>Upload Single File</h3>
				<form id="uploadSingleFileForm">
					<div class="form-group">
						<label class="control-label" for="uploadfile">Choose a File:</label>
						<input type="file" class="form-control" 
								placeholder="Choose a upload file" name="uploadfile" required></input>
					</div>
					<button type="submit" class="btn btn-danger" id="btnUploadSingleFileSubmit">Submit</button>
				</form>
				<div id="response" style="display:none">
				</div>
			</div>
		</div>
		<hr>		
		<div class="row">
			<div class="col-sm-7" style="background-color:#e6fffa; padding:10px; border-radius:3px">
				<h3>Upload Multiple Files</h3>
				<form id="uploadMultipleFilesForm">
					<div class="form-group">
						<label class="control-label" for="uploadfiles">Choose Files:</label>
						<input type="file" class="form-control" 
								placeholder="Choose upload files" name="uploadfiles" multiple required></input>
					</div>
					<button type="submit" class="btn btn-danger" id="btnUploadMultipleFilesSubmit">Submit</button>
				</form>
				<div id="responses" style="display:none">
				</div>
			</div>
			
		</div>
		<hr>
		<div id="uploadedfiles" class="row">
			<div class="col-sm-7">
				<button type="button" class="btn btn-primary" id="btnGetFiles">All Files</button>
				<div id="uploadedfiles">
				</div>
			</div>
		</div>
	</div>
</body>
</html>

Implement Ajax Upload Download Files

We use JQuery Ajax to submit html upload file forms and get a list names of uploaded files:

/**
 * Copyright by https://loizenjava.com
 * Author: loizenjava.com 
 */

$(document).ready(function() {
	
	/**
	 * Upload single file to SpringBoot 
	 * at RestAPI: /api/upload/file/single
	 */
	$("#uploadSingleFileForm").submit(function(evt) {
		evt.preventDefault();
		
		var formData = new FormData($(this)[0]);
		
		$.ajax({
			url : '/api/upload/file/single',
			type : 'POST',
			data : formData,
			async : false,
			cache : false,
			contentType : false,
			enctype : 'multipart/form-data',
			processData : false,
			success : function(response) {
				$("#response").empty();
				if(response.errStatus !== "error"){
					var displayInfo = response.messages[0].filename + " : " + response.messages[0].message + "<br>"; 
					
					$("#response").append(displayInfo);
					
					var downloadLink = "http://localhost:8080/api/download/file/" + response.messages[0].filename;
					var downloadAt = "&nbsp;&nbsp;&nbsp; -> Download File: " + "<a href=" + "\'" + downloadLink + "\'>" + downloadLink + "</a>";
					
					$("#response").append(downloadAt);
					
					// add some css
					$("#response").css("display", "block");
					$("#response").css("background-color", "#e6e6ff");
					$("#response").css("border", "solid 1px black");
					$("#response").css("border-radius", "3px");
					$("#response").css("margin", "10px");
					$("#response").css("padding", "10px");
				}else{
					$("#response").css("display", "none");
					var error = response.error.errDesc;
					alert(error);
				}
			},
			error: function(e){
				alert("Fail! " + e);
			}
		});
		
		return false;
	});
	

	/**
	 * Upload Multiple Files to SpringBoot RestAPI
	 */
	$("#uploadMultipleFilesForm").submit(function(evt) {
		evt.preventDefault();
		
		var formData = new FormData($(this)[0]);
		
		$.ajax({
			url : '/api/upload/file/multiple',
			type : 'POST',
			data : formData,
			async : false,
			cache : false,
			contentType : false,
			enctype : 'multipart/form-data',
			processData : false,
			success : function(response) {
				
				$("#responses").empty();
				if(response.errStatus !== "error"){
					
					var displayInfo = "<ul>";
					
					for(var i=0; i<response.messages.length; i++){
						
						displayInfo += "<li>" + response.messages[i].filename + "&nbsp; : &nbsp;" + response.messages[i].message;
						
						if (response.messages[i].status === "ok") {
							var downloadLink = "http://localhost:8080/api/download/file/" + response.messages[i].filename;
							var downloadAt = "&nbsp;&nbsp;&nbsp; -> Link: " + "<a href=" + "\'" 
												+ downloadLink + "\'>" + downloadLink + "</a>";
							
							displayInfo += "<br>" + downloadAt;  
						}
						
						displayInfo += "</li>";
					}
					$("#responses").append(displayInfo + "</ul>");
					$("#responses").css("display", "block");
					
					// add some css
					$("#responses").css("background-color", "#e6e6ff");
					$("#responses").css("border", "solid 1px black");
					$("#responses").css("border-radius", "3px");
					$("#responses").css("margin", "10px");
					$("#responses").css("padding", "10px");
				}else{
					$("#responses").css("display", "none");
					var error = response.error.errDesc;
					alert(error);
				}
			},
			error: function(e){
				alert("Fail! " + e);
			}
		});
		
		return false;
	});
	
	/**
	 * Get all uploaded files and download-links
	 */
	$( "#btnGetFiles").click(function() {
		$.get('/api/download/files', function (response, textStatus, jqXHR) {  // success callback
			
			var files = "<ul>";
			
			for(var i=0; i<response.fileInfos.length; i++) {
				files += "<li>" + response.fileInfos[i].filename + "<br>" 
									+ "&nbsp;&nbsp;&nbsp; Link -> <a href=" + "\'" + response.fileInfos[i].url + "\'" + ">"
									+ response.fileInfos[i].url 
									+ "</a></li>"
			}
			
			files += "</ul>";
			
			$("#uploadfiles").append(files);
			
			// add some css
			$("#uploadfiles").css("background-color", "#e6e6ff");
			$("#uploadfiles").css("border", "solid 1px black");
			$("#uploadfiles").css("border-radius", "3px");
			$("#uploadfiles").css("margin", "10px");
			$("#uploadfiles").css("padding", "10px");
		});
	});
})

Testing Ajax Upload/Download Client

- Upload single file:

Test case with html form - Upload single file
Test case with html form - Upload single file

- Upload multiple files:

Testcase with Html Upload Form - Upload Multiple Files
Testcase with Html Upload Form - Upload Multiple Files

- Get a list of uploaded files' name and download them:

Get list of uploaded files and download them
Get list of uploaded files and download them

Sourcecode

All features of sourcecode for the tutorial SpringBoot Upload Download Multiple Files with Rest API + Ajax:

  • Backend
    • Upload Single File RestAPI
    • Upload Multiple Files RestAPI
    • Get All Uploaded Files' Names
    • Download a Uploaded File with a specific name
  • Frontend
    • Html Upload Single File Form
    • Html Upload Multiple Files Form
    • JQuery Ajax to get a list names of uploaded files

- GitHub Sourcecode:

10 thoughts on “SpringBoot Upload Download Multiple Files with Rest API + Ajax Tutorial”

Leave a Reply

Your email address will not be published. Required fields are marked *