Downloading and uploading files is a common task in web development! Personally, I worked on a project where I was required to implement file upload/download logic whith Spring Boot!

In this article, I will cover in depth how to download and upload files using Spring Boot RESTful web services.! I will walk you through the whole process step by step!

So, let’s get started!

Project requirements

What we will use?

To complete this tutorial, you need to install the following tools on your machine:

  • Java 8+

  • JDK 1.8+

  • Your favorite Java IDE

  • Postman (optional)

What we will build ?

To exemplify how to download and upload files, we will build a Spring Boot web application that handles file upload, download and storage!

In general, our simple project will be designed around two important things:

  • Service layer to hold file storage logic.

  • One single controller for handling upload and download requests/responses.

Let’s get into coding!

Setup Spring Boot project

In this tutorial, we will use Spring Boot CLI to quickly scaffold and prototype our project!

Open your terminal and type the following command lines:

    
        C:\Users\azhwani>spring init --groupId=com.api --artifactId=downupload --name=downupload --dependencies=web,lombok downupload
        Using service at https://start.spring.io
        Project extracted to 'C:\Users\azhwani\downupload'
        C:\Users\azhwani>
    

You are all set! Cool, right ?

You have got a project with all necessary files and dependencies that are needed to get started!

Now, you can import your project into your favorite Java IDE and start coding right away :)

Project Structure

By default, Spring Boot generates projects with the following directory structure:

    
        ├── src
        │   └── main
        │   │   ├── java
        │   │   │   └── com
        │   │   │       └── api
        │   │   │           └── downupload
        │   │   │               └── DownuploadApplication.java
        │   │   └── resources
        │   │       └── static 
        │   │       └── templates 
        │   │       └── application.properties
        │   │
        │   └── test
        │       └── java
        │           └── com
        │               └── api
        │                   └── downupload
        │                       └── DownuploadApplicationTests.java
        └── pom.xml
    

As you can see, the directory tree of our project is organized around some importants folders and files:

  • pom.xml : contains details about all dependencies used by Maven to build the project.

  • DownuploadApplication.java : An entry point class with a main() method to bootstrap the application.

  • DownuploadApplicationTests.java : An empty test class.

  • application.properties : a special file that can be used to store configuration properties!

Project Dependencies

The following listing shows how pom.xml file of our project looks like:

   
    
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.6.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.api</groupId>
        <artifactId>downupload</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>downupload</name>
        <description>Download and upload files with Spring Boot</description>

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

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    

Spring Boot REST API for file upload/download

In this section, we are going to use Spring Boot to build a backend API that exposes three REST endpoints:

  • POST /api/uploadfile to upload one single file.

  • POST /api/uploadfiles to upload multiple files.

  • GET /api/download/{filename:.+} to download a file.

MultipartFile interface

Simple put, you need to construct HTTP multipart requests to upload files over to your server!

You can explore more about multipart requests here.

Spring relies on MultipartFile interface to handle HTTP multi-part requests! MultipartFile encapsulates an uploaded file received in a multipart request!

Configuring file upload properties

First, let’s configure multipart settings! It is always pratical to define the maximum file size that can be uploaded when dealing with multipart requests!

Add the following properties to src/main/resources/application.properties file and Spring Boot will take care of the rest!

    
        ## Multipart config
        spring.servlet.multipart.enabled=true
        spring.servlet.multipart.file-size-threshold=2KB
        spring.servlet.multipart.max-file-size=200MB
        spring.servlet.multipart.max-request-size=215MB

        ## File upload custom properties
        file.upload.location=/Users/uploads
    

Spring Boot does not provide any default property that can be used to specify where all your files will be uploaded!

So, we defined this custom property file.upload.location to configure the directory path where you want to store the uploaded files!

Now, we need just to tell Spring Boot about it!

Binding custom properties

Spring Boot comes with a handy feature called ConfigurationProperties to help us access all the custom properties defined in application.properties file.

@ConfigurationProperties provides a way to map property files into Java classes!

Now, let’s create a simple Java class FileUploadProperties to bind our custom property file.upload.location!

    
        package com.api.downupload.properties;
        import org.springframework.boot.context.properties.ConfigurationProperties;
        
        @ConfigurationProperties(prefix = "file.upload")
        public class FileUploadProperties {
            private String location;
            public String getLocation() {
                return location;
            }
            public void setLocation(String location) {
                this.location = location;
            }
        }
    

As you can see, we have defined getter and setter methods for “location” field! Spring uses JavaBean specification to do the mapping between file properties and Java fields!

@ConfigurationProperties(prefix = “file.upload”) will simply bind all the properties prefixed with file.upload to the corresponding class fields!

To enable ConfigurationProperties feature, you need to add @EnableConfigurationProperties annotation to DownuploadApplication.java class, the starting point of our application!

    
        package com.api.downupload;

        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        import org.springframework.boot.context.properties.EnableConfigurationProperties;
        import com.api.downupload.properties.FileUploadProperties;

        @SpringBootApplication
        @EnableConfigurationProperties({
            FileUploadProperties.class
        })
        public class DownuploadApplication {
            public static void main(String[] args) {
                SpringApplication.run(DownuploadApplication.class, args);
            }
        }
    

Create service layer for file storage

Our Spring controller needs to interact with the file system to store or retrieve files! So, to promote good separation of concerns, we are going to create a service layer to hold the business logic!

First, we are going to create a simple interface IFileSytemStorage with the following methods:

    
        package com.api.downupload.service;
        import org.springframework.core.io.Resource;
        import org.springframework.web.multipart.MultipartFile;

        public interface IFileSytemStorage {
            void init();
            String saveFile(MultipartFile file);
            Resource loadFile(String fileName);
        }
    

Now, let’s create an implementation of our interface with the following code:

    
        package com.api.downupload.service;

        import java.net.MalformedURLException;
        import java.nio.file.Files;
        import java.nio.file.Path;
        import java.nio.file.Paths;
        import java.nio.file.StandardCopyOption;
        import javax.annotation.PostConstruct;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.core.io.Resource;
        import org.springframework.core.io.UrlResource;
        import org.springframework.stereotype.Service;
        import org.springframework.web.multipart.MultipartFile;
        import com.api.downupload.exception.FileNotFoundException;
        import com.api.downupload.exception.FileStorageException;
        import com.api.downupload.properties.FileUploadProperties;

        @Service
        public class FileSystemStorageService implements IFileSytemStorage {
            private final Path dirLocation;
            @Autowired
            public FileSystemStorageService(FileUploadProperties fileUploadProperties) {
                this.dirLocation = Paths.get(fileUploadProperties.getLocation())
                                        .toAbsolutePath()
                                        .normalize();
            }
            
            @Override
            @PostConstruct
            public void init() {
                // TODO Auto-generated method stub
                try {
                    Files.createDirectories(this.dirLocation);
                } 
                catch (Exception ex) {
                    throw new FileStorageException("Could not create upload dir!");
                }
            }

            @Override
            public String saveFile(MultipartFile file) {
                // TODO Auto-generated method stub
                try {
                    String fileName = file.getOriginalFilename();
                    Path dfile = this.dirLocation.resolve(fileName);
                    Files.copy(file.getInputStream(), dfile,StandardCopyOption.REPLACE_EXISTING);
                    return fileName;
                    
                } catch (Exception e) {
                    throw new FileStorageException("Could not upload file");
                }
            }

            @Override
            public Resource loadFile(String fileName) {
                // TODO Auto-generated method stub
                try {
                  Path file = this.dirLocation.resolve(fileName).normalize();
                  Resource resource = new UrlResource(file.toUri());

                  if (resource.exists() || resource.isReadable()) {
                      return resource;
                  } 
                  else {
                      throw new FileNotFoundException("Could not find file");
                  }
                } 
                catch (MalformedURLException e) {
                    throw new FileNotFoundException("Could not download file");
                }           
            }
        }
    

Handle file upload/download exceptions

In this section, we will write the necessary code to handle file related exceptions that might be thrown when downloading or uploading files!

FileStorageException will be raised in case of unexpected scenarios that may occur when storing the uploaded files!

    
        package com.api.downupload.exception;
        import lombok.AllArgsConstructor;
        import lombok.Getter;
        import lombok.Setter;

        @Getter
        @Setter
        @AllArgsConstructor
        public class FileStorageException extends RuntimeException {
            private String message;
        }
    

FileNotFoundException will be thrown when the requested file does not exist!

    
        package com.api.downupload.exception;
        import lombok.AllArgsConstructor;
        import lombok.Getter;
        import lombok.Setter;

        @Getter
        @Setter
        @AllArgsConstructor
        public class FileNotFoundException extends RuntimeException {
            private String message;
        }
    

Spring provides @ControllerAdvice annotation to help us centralize exception handling logic across the whole application in one global handler class!

The idea is very simple, you just need to create a Java class annotated with @ControllerAdvice:

    
        package com.api.downupload.exception;
        import java.time.LocalDateTime;
        import java.util.ArrayList;
        import java.util.List;
        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.multipart.MaxUploadSizeExceededException;
        import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
        import com.api.downupload.model.ResponseError;

        @ControllerAdvice
        public class FileExceptionAdvice extends ResponseEntityExceptionHandler {
            @ExceptionHandler(FileNotFoundException.class)
            public ResponseEntity<Object> handleFileNotFoundException(FileNotFoundException exc) {
                
                List<String> details = new ArrayList<String>();
                details.add(exc.getMessage());
                ResponseError err = new ResponseError(LocalDateTime.now(), "File Not Found" ,details);
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(err);
            }
            
            @ExceptionHandler(MaxUploadSizeExceededException.class)
            public ResponseEntity<Object> handleMaxSizeException(MaxUploadSizeExceededException exc) {
                
                List<String> details = new ArrayList<String>();
                details.add(exc.getMessage());
                ResponseError err = new ResponseError(LocalDateTime.now(), "File Size Exceeded" ,details);
                return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(err);
            }
        }
    

@ExceptionHandler is used to bind a method to a specific exception!

For example, handleFileNotFoundException() is activated when FileNotFoundException is raised!

ResponseError class represents error responses in a more concise and meaningful way!

    
        package com.api.downupload.model;
        import java.time.LocalDateTime;
        import java.util.List;
        import com.fasterxml.jackson.annotation.JsonFormat;
        import lombok.AllArgsConstructor;
        import lombok.Data;

        @Data
        @AllArgsConstructor
        public class ResponseError {
            @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
            private LocalDateTime timestamp;
            private String message;
            private List erros;
        }
    

Create controller for downloading and uploading files

Now, let’s create a new Spring controller class inside com.api.downupload.controller package to handle HTTP multi-part requests for downloading and uploading files!

    
        import org.springframework.web.bind.annotation.RestController;
        import org.springframework.web.multipart.MultipartFile;
        import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
        import com.api.downupload.model.FileResponse;
        import com.api.downupload.service.IFileSytemStorage;

        @RestController
        @RequestMapping("/api")
        public class FileController {
            @Autowired
            IFileSytemStorage fileSytemStorage;
            
            // POST "/uploadfile"
            // POST "/uploadfiles"
            // GET  "/download/{filename}"
        }
    

Note that, FileController uses a reference of IFileSytemStorage service (holds business logic) to deal with file download/upload requests!

FileResponse class

This class will be used to encapsulate HTTP response body returned from our REST API endpoints!

    
        package com.api.downupload.model;
        import lombok.AllArgsConstructor;
        import lombok.Data;

        @Data
        @AllArgsConstructor
        public class FileResponse {
            private String fileName;
            private String fileUrl;
            private String message;
        }
    

Upload Single File

    
        @PostMapping("/uploadfile")
        public ResponseEntity<FileResponse> uploadSingleFile (@RequestParam("file") MultipartFile file) {
            String upfile = fileSytemStorage.saveFile(file);

            String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
                    .path("/api/download/")
                    .path(upfile)
                    .toUriString();
            
            return ResponseEntity.status(HttpStatus.OK).body(new FileResponse(upfile,fileDownloadUri,"File uploaded with success!"));
        }
    

@PostMapping maps POST requests to handler methods! We used it to map our POST /api/uploadfile request to uploadSingleFile() handler method!

@RequestParam binds request parameter file to MultipartFile object passed as a parameter to uploadSingleFile() method!

Upload Multiple Files

    
        @PostMapping("/uploadfiles")
        public ResponseEntity<List<FileResponse>> uploadMultipleFiles (@RequestParam("files") MultipartFile[] files) {
            
            List<FileResponse> responses = Arrays
                .asList(files)
                .stream()
                .map(
                    file -> {
                        String upfile = fileSytemStorage.saveFile(file);
                        String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
                                .path("/api/download/")
                                .path(upfile)
                                .toUriString();
                        return new FileResponse(upfile,fileDownloadUri,"File uploaded with success!");
                    }
                )
                .collect(Collectors.toList());
            return ResponseEntity.status(HttpStatus.OK).body(responses);
        }
    

Here, we used Java 8 Stream API to simplify the logic of uploading all the files encapsulated in MultipartFile array!

In general, map() method is used to transform an object of type T to another object of type R! We used a lambda expression to handle file upload process and return an object of FileResponse class!

Download File

    
        @GetMapping("/download/{filename:.+}")
        public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
           
            Resource resource = fileSytemStorage.loadFile(filename);

            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
                    .body(resource);
        }
    

@GetMapping is used to map HTTP GET requests onto specific handler methods!

ResponseEntity class helps us generate a more significant HTTP response with all the details we need : appropriate status code and body!

Run and Test File download/upload API:

Let’s run our Spring Boot application! Awesome! As you can see, the application is running without any issue!

    
          .   ____          _            __ _ _
         /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
        ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
         \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
          '  |____| .__|_| |_|_| |_\__, | / / / /
         =========|_|==============|___/=/_/_/_/
         :: Spring Boot ::        (v2.2.6.RELEASE)

        2020-04-28 16:01:37.970  INFO 2184 --- [           main] c.api.downupload.DownuploadApplication   : Starting DownuploadApplication on azhwani-PC with PID 2184 (C:\Users\azhwani\git\downupload\target\classes started by azhwani in C:\Users\azhwani\git\downupload)
        2020-04-28 16:01:37.979  INFO 2184 --- [           main] c.api.downupload.DownuploadApplication   : No active profile set, falling back to default profiles: default
        2020-04-28 16:01:41.951  INFO 2184 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 9000 (http)
        2020-04-28 16:01:41.979  INFO 2184 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
        2020-04-28 16:01:41.980  INFO 2184 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.33]
        2020-04-28 16:01:42.402  INFO 2184 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
        2020-04-28 16:01:42.403  INFO 2184 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 4289 ms
        2020-04-28 16:01:43.314  INFO 2184 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
        2020-04-28 16:01:43.618  INFO 2184 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page template: index
        2020-04-28 16:01:44.216  INFO 2184 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 9000 (http) with context path ''
        2020-04-28 16:01:44.221  INFO 2184 --- [           main] c.api.downupload.DownuploadApplication   : Started DownuploadApplication in 7.519 seconds (JVM running for 9.191)
    

Now, it is time to test our REST API endpoints!

Testing with Postman

Uploading file with POST /api/uploadfile

Test upload single file with postman

Uploading multiple files with POST /api/uploadfiles

Test upload multiple files with postman

Downloading file with GET /api/download/{filename}

Test download file with postman

File Not Found Exception

Test download file with postman

Consuming REST API using jQuery Ajax!

Awesome! Our backend Spring Boot REST API is working fine! Now, let’s develop a simple frontend interface with some AJAX calls to consume our RESTful web services!

In Spring Boot applications, all static resources are located in src/main/resources/static folder!

Let’s get into business!

index.html

    
        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="utf-8"/>
                <title>Upload Single File</title>
                
                <link href="http://fonts.googleapis.com/css?family=PT+Sans+Narrow:400,700" rel="stylesheet"/>
                <link href="/css/style.css" rel="stylesheet"/>
            </head>

            <body>
                <form id="uploadfile" id="uploadfile" class="upload">
                    <div id="drop">
                        Drop Here
                        <input type="file" name="file"/>
                    </div>
                    
                    <button type="submit" class="btn">Upload File</button>

                    <ul id="uploadresult">
                    </ul>
                </form>

                <footer>
                    <h2><a href="http://devwithus.com"><i>Tutorial BY </i> azhwani - devwithus.com</a></h2>      
                </footer>
                
                <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
                <script src="/js/app.js"></script>
                
            </body>
        </html>
    

app.js

    
        $(document).ready(function () {
            /* Upload single File   */
            $("#uploadfile").submit(function(event) {
                $.ajax({
                    type: 'POST',
                    enctype: 'multipart/form-data',
                    url: '/api/uploadfile',
                    data: new FormData(this),
                    contentType: false,
                    cache: false,
                    processData:false,
                    success: function(response){
                        $("#uploadresult").html("<li><p>"+response.fileName+"<i><a href='"+response.fileUrl+"'>"+response.fileUrl+"</a></i></p><span></span></li>");
                    },
                    error: function (error) {
                        console.log(error);
                    }
                });
                event.preventDefault();
            });
            
            /* Upload multiple Files   */
            $("#uploadmultiple").submit(function(event) {
                $.ajax({
                    type: 'POST',
                    enctype: 'multipart/form-data',
                    url: '/api/uploadfiles',
                    data: new FormData(this),
                    contentType: false,
                    cache: false,
                    processData:false,
                    success: function(response){
                        let content  = '';
                        $.each(response, function( i, v ) {
                            content += "<li><p>"+v.fileName+"<i><a href='"+v.fileUrl+"'>"+v.fileUrl+"</a></i></p><span></span></li>";
                        });
                        $("#uploadresult").html(content);
                    },
                    error: function (error) {
                        console.log(error);
                    }
                });
                event.preventDefault();
            });
        });
    

style.css

    
        *{
            margin:0;
            padding:0;
        }

        html{
            background-color:#ebebec;
            background-image:-webkit-radial-gradient(center, #ebebec, #b4b4b4);
            background-image:-moz-radial-gradient(center, #ebebec, #b4b4b4);
            background-image:radial-gradient(center, #ebebec, #b4b4b4);
            min-height:900px;
        }

        body{
            font:15px/1.3 Arial, sans-serif;
            color: #4f4f4f;
        }

        a, a:visited {
            outline:none;
            color:#389dc1;
        }

        a:hover{
            text-decoration:none;
        }

        section, footer, header, aside{
            display: block;
        }

        .btn{
            background-color:#007a96;
            padding:12px 26px;
            color:#fff;
            font-size:14px;
            border-radius:2px;
            cursor:pointer;
            display:inline-block;
            line-height:1;
            text-transform: uppercase;
            width:100%;
        }

        .upload{
            font-family:'PT Sans Narrow', sans-serif;
            background-color:#373a3d;
            background-image:-webkit-linear-gradient(top, #373a3d, #313437);
            background-image:-moz-linear-gradient(top, #373a3d, #313437);
            background-image:linear-gradient(top, #373a3d, #313437);
            width:400px;
            padding:30px;
            border-radius:3px;
            margin:100px auto 100px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
        }

        #drop{
            background-color: #2E3134;
            margin-bottom: 30px;
            border: 20px solid rgba(0, 0, 0, 0);
            border-radius: 3px;
            border-image: url('../img/border-image.png') 25 repeat;
            text-align: center;
            text-transform: uppercase;
            font-size:16px;
            font-weight:bold;
            color:#7f858a;
            height:80px;
        }

        #drop > input[type="file"]{
            top: 0;
            right: 0;
            opacity: 0;
            cursor: pointer;
            width:100%;
            height:100%;
        }

        .upload ul{
            list-style:none;
            margin:0 -30px;
            border-top:1px solid #2b2e31;
            border-bottom:1px solid #3d4043;
        }

        .upload ul li{
            background-color:#333639;
            background-image:-webkit-linear-gradient(top, #333639, #303335);
            background-image:-moz-linear-gradient(top, #333639, #303335);
            background-image:linear-gradient(top, #333639, #303335);
            border-top:1px solid #3d4043;
            border-bottom:1px solid #2b2e31;
            padding:15px;
            height: 52px;
            position: relative;
        }

        .upload ul li input{
            display: none;
        }

        .upload ul li p{
            overflow: hidden;
            white-space: nowrap;
            color: #EEE;
            font-size: 16px;
            font-weight: bold;
            position: absolute;
            top: 20px;
            left: 30px;
        }

        .upload ul li i{
            font-weight: normal;
            font-style:normal;
            color:#7f7f7f;
            display:block;
        }

        .upload ul li canvas{
            top: 15px;
            left: 32px;
            position: absolute;
        }

        .upload ul li span{
            width: 15px;
            height: 12px;
            background: url('../img/icons.png') no-repeat;
            position: absolute;
            top: 34px;
            right: 33px;
            cursor:pointer;
        }

        .upload ul li.working span{
            height: 16px;
            background-position: 0 -12px;
        }

        .upload ul li.error p{
            color:red;
        }

        footer{
            background-color: #080808;
            bottom: 0;
            box-shadow: 0 -1px 2px rgba(0,0,0,0.4);
            height: 40px;
            left: 0;
            position: fixed;
            width: 100%;
            z-index: 100000;
        }

        footer h2{
            font-size: 14px;
            font-weight: normal;
            position: absolute;
            top:0;
            left: 50%;
            bottom:0;
            margin-left: -420px;
            padding:13px 20px 0;
            border:1px solid #000;
            border-width:0 1px;
            background-color:rgba(0,0,0,0.6);
            box-shadow:0 0 5px #000 inset;
            line-height: 1;
            margin-top: 0;
        }

        footer h2 a{
            color: #EEEEEE !important;
            text-decoration:none !important;
        }

        footer h2 i{
            font-style:normal;
            color:#888;
        }

    

Some screenshots:

Conclusion

That’s all my friend! You have learned how to download and upload files using RESTful Spring Boot API.

You can find the complete source code for this tutorial on this Github repository https://github.com/devwithus/upload-file-with-spring-boot

Stay safe and take care!