Treatment uploading and downloading files are very common jobs in about of the web applications. Spring Boot provides the MultipartFile interface to handle HTTP multi-role requests for uploading files.

In this tutorial, nosotros will learn the post-obit:

  • Create a Spring Boot spider web application that allows file uploads
  • Upload single and multiple files using RESTful web services
  • Download file using RESTful web service
  • Listing all files uploaded on the server
  • A simple Thymeleaf & HTML web interface to upload file(southward) from browser

Tools you need to complete this tutorial:

  • Java viii+
  • JDK i.8+
  • Spring Boot
  • Thymeleaf
  • Gradle iv+
  • Postman (optional for testing RESTful APIs)

Note: This article uses RESTful web services to upload and download files in Jump Boot. If you are using Thymeleaf and want to upload a file, check out this guide.

Project Dependencies

We only demand spring-boot-starter-web and spring-kick-starter-thymeleaf starter dependencies for our case Spring Kicking projection. We do not need whatever actress dependency for file upload. Hither is how our build.gradle file looks like:

build.gradle

          plugins            {            id            'org.springframework.boot'            version            '2.1.three.RELEASE'            id            'coffee'            }            apply plugin:            'io.leap.dependency-management'            grouping            =            'com.attacomsian'            version            =            '0.0.i-SNAPSHOT'            sourceCompatibility            =            'ane.8'            repositories            {            mavenCentral            (            )            }            dependencies            {            implementation            'org.springframework.kicking:bound-boot-starter-thymeleaf'            implementation            'org.springframework.boot:spring-boot-starter-web'            }                  

I used Bound Initializr to generate the above Gradle configuration file. It is an easier and quicker way to create a Spring Boot application.

Configure Properties

Before nosotros start the bodily work, let'due south first configure the location on the server where all the uploaded files will be stored. We'll too configure the maximum file size that can be uploaded in a single HTTP multi-part request. Bound Boot automatically enables multipart/form-information requests, so nosotros practise non need to do anything.

awarding.properties

                      # max file size            jump.servlet.multipart.max-file-size            =            10MB            # max request size            spring.servlet.multipart.max-request-size            =            10MB            # files storage location (stores all files uploaded via Balance API)            storage.location            =            ./uploads                  

In above properties file, we have ii multi-part settings:

  • jump.servlet.multipart.max-file-size is set to 10MB, which ways total files size cannot exceed 10MB.
  • spring.servlet.multipart.max-asking-size sets the maximum multipart/course-data request size to 10MB.

In simple words, we cannot upload files greater than 10MB in size given the above configuration.

Enable Configuration Properties

In our application.properties file, nosotros define the storage location. Now permit's create a POJO course called StorageProperties and annotate information technology with @ConfigurationProperties to automatically bind the properties defined in application.properties file.

StorageProperties.java

                      packet            com.attacomsian.uploadfiles.storage            ;            import            org.springframework.boot.context.properties.                        ConfigurationProperties            ;            @ConfigurationProperties            (prefix            =            "storage"            )            public            class            StorageProperties            {            individual            String            location;            public            Cord            getLocation            (            )            {            return            location;            }            public            void            setLocation            (            String            location)            {            this            .location            =            location;            }            }                  

Notice the prefix= "storage" attribute in the above annotation. It instructs @ConfigurationProperties to bind all the properties that start with storage prefix to their corresponding attributes of POJO class when the application is started.

The adjacent pace is to enable the ConfigurationProperties feature past adding @EnableConfigurationProperties notation to our master configuration class.

Application.java

                      package            com.attacomsian.uploadfiles            ;            import            com.attacomsian.uploadfiles.storage.                        StorageProperties            ;            import            org.springframework.boot.                        SpringApplication            ;            import            org.springframework.boot.autoconfigure.                        SpringBootApplication            ;            import            org.springframework.boot.context.backdrop.                        EnableConfigurationProperties            ;            @SpringBootApplication            @EnableConfigurationProperties            (            StorageProperties            .            class            )            public            class            Application            {            public            static            void            master            (            String            [            ]            args)            {            SpringApplication            .            run            (            Application            .            class            ,            args)            ;            }            }                  

Files Upload Controller

Let's at present create a controller course called FileController for treatment uploading and downloading files via RESTful web services. It likewise defines a road to list all the uploaded files.

FileController.coffee

                      package            com.attacomsian.uploadfiles.controllers            ;            import            com.attacomsian.uploadfiles.commons.                        FileResponse            ;            import            com.attacomsian.uploadfiles.storage.                        StorageService            ;            import            org.springframework.core.io.                        Resource            ;            import            org.springframework.http.                        HttpHeaders            ;            import            org.springframework.http.                        ResponseEntity            ;            import            org.springframework.stereotype.                        Controller            ;            import            org.springframework.ui.                        Model            ;            import            org.springframework.spider web.demark.note.                        *            ;            import            org.springframework.web.multipart.                        MultipartFile            ;            import            org.springframework.web.servlet.support.                        ServletUriComponentsBuilder            ;            import            java.util.                        Arrays            ;            import            coffee.util.                        List            ;            import            java.util.stream.                        Collectors            ;            @Controller            public            form            FileController            {            private            StorageService            storageService;            public            FileController            (            StorageService            storageService)            {            this            .storageService            =            storageService;            }            @GetMapping            (            "/"            )            public            String            listAllFiles            (            Model            model)            {            model.            addAttribute            (            "files"            ,            storageService.            loadAll            (            )            .            map            (            path            ->            ServletUriComponentsBuilder            .            fromCurrentContextPath            (            )            .            path            (            "/download/"            )            .            path            (path.            getFileName            (            )            .            toString            (            )            )            .            toUriString            (            )            )            .            collect            (            Collectors            .            toList            (            )            )            )            ;            return            "listFiles"            ;            }            @GetMapping            (            "/download/{filename:.+}"            )            @ResponseBody            public            ResponseEntity                          <              Resource              >                        downloadFile            (            @PathVariable            String            filename)            {            Resource            resource            =            storageService.            loadAsResource            (filename)            ;            return            ResponseEntity            .            ok            (            )            .            header            (            HttpHeaders            .CONTENT_DISPOSITION,            "zipper; filename=\""            +            resource.            getFilename            (            )            +            "\""            )            .            body            (resource)            ;            }            @PostMapping            (            "/upload-file"            )            @ResponseBody            public            FileResponse            uploadFile            (            @RequestParam            (            "file"            )            MultipartFile            file)            {            String            name            =            storageService.            store            (file)            ;            String            uri            =            ServletUriComponentsBuilder            .            fromCurrentContextPath            (            )            .            path            (            "/download/"            )            .            path            (name)            .            toUriString            (            )            ;            return            new            FileResponse            (name,            uri,            file.            getContentType            (            )            ,            file.            getSize            (            )            )            ;            }            @PostMapping            (            "/upload-multiple-files"            )            @ResponseBody            public            List                          <              FileResponse              >                        uploadMultipleFiles            (            @RequestParam            (            "files"            )            MultipartFile            [            ]            files)            {            render            Arrays            .            stream            (files)            .            map            (file            ->            uploadFile            (file)            )            .            collect            (            Collectors            .            toList            (            )            )            ;            }            }                  

As ever, our controller course is annotated with @Controller to let the Bound MVC pick it upward for routes. Each method is decorated with @GetMapping or @PostMapping to demark the path and the HTTP action with that particular method.

  • Become / loads the current list of uploaded files and renders information technology into a Thymeleaf template called listFiles.html.
  • POST /download/{filename} resolves the resource if it exists, and sends it to the browser for download. HttpHeaders.CONTENT_DISPOSITION adds the "Content-Disposition" response header to indicate file attachment.
  • POST /upload-file & /upload-multiple-files routes handle HTTP multi-part requests and use StorageService for saving files on the server. Both these methods render an object of FileResponse afterward the upload is finished.

The FileResponse course is used to return a JSON response for RESTful web services.

FileResponse.java

                      bundle            com.attacomsian.uploadfiles.eatables            ;            public            class            FileResponse            {            private            String            name;            private            Cord            uri;            private            String            type;            private            long            size;            public            FileResponse            (            String            name,            String            uri,            String            type,            long            size)            {            this            .name            =            proper noun;            this            .uri            =            uri;            this            .type            =            type;            this            .size            =            size;            }            // getters and setters removed for the sake of brevity            }                  

The FileController form uses the StorageService interface for storing and resolving files in the file system. It is the about important course for handling files in our instance. We'll define these classes in the next section.

In production, information technology's not advised to store the uploaded files in your application file organisation. Y'all might lose all files if your awarding server is damaged. It also makes very difficult to move the awarding from ane server to some other. Therefore, information technology is a skilful practise to apply external storage like AWS S3 for storing all the uploaded files. I'll write near this topic in the future.

Storage Service

Finally, it is fourth dimension to create a storage service called StorageService for our controller to connect with a storage layer (eastward.g. file system in our case). This task involves several classes. Nosotros'll define these classes one-past-one.

The kickoff pace is to ascertain an interface called StorageService as shown below:

StorageService.java

                      packet            com.attacomsian.uploadfiles.storage            ;            import            org.springframework.core.io.                        Resource            ;            import            org.springframework.web.multipart.                        MultipartFile            ;            import            java.nio.file.                        Path            ;            import            java.util.stream.                        Stream            ;            public            interface            StorageService            {            void            init            (            )            ;            Cord            shop            (            MultipartFile            file)            ;            Stream                          <              Path              >                        loadAll            (            )            ;            Path            load            (            Cord            filename)            ;            Resource            loadAsResource            (            String            filename)            ;            void            deleteAll            (            )            ;            }                  

The above interface declares several abstract methods for initializing, storing, removing and retrieving files. It but lists possible storage operations without their implementation. Now, it is up to y'all to decide how you want to implement them. In this example, nosotros will use our file system for handling files. Information technology can as well be implemented to store the files on any external location.

Let's create a concrete class FileSystemStorageService that implements the StorageService interface.

FileSystemStorageService.java

                      bundle            com.attacomsian.uploadfiles.storage            ;            import            org.springframework.beans.factory.note.                        Autowired            ;            import            org.springframework.core.io.                        Resource            ;            import            org.springframework.cadre.io.                        UrlResource            ;            import            org.springframework.stereotype.                        Service            ;            import            org.springframework.util.                        FileSystemUtils            ;            import            org.springframework.util.                        StringUtils            ;            import            org.springframework.spider web.multipart.                        MultipartFile            ;            import            javax.annotation.                        PostConstruct            ;            import            java.io.                        IOException            ;            import            java.io.                        InputStream            ;            import            java.net.                        MalformedURLException            ;            import            java.nio.file.                        Files            ;            import            java.nio.file.                        Path            ;            import            coffee.nio.file.                        Paths            ;            import            coffee.nio.file.                        StandardCopyOption            ;            import            coffee.util.stream.                        Stream            ;            @Service            public            class            FileSystemStorageService            implements            StorageService            {            private            last            Path            rootLocation;            @Autowired            public            FileSystemStorageService            (            StorageProperties            properties)            {            this            .rootLocation            =            Paths            .            get            (backdrop.            getLocation            (            )            )            ;            }            @Override            @PostConstruct            public            void            init            (            )            {            try            {            Files            .            createDirectories            (rootLocation)            ;            }            catch            (            IOException            eastward)            {            throw            new            StorageException            (            "Could non initialize storage location"            ,            eastward)            ;            }            }            @Override            public            Cord            shop            (            MultipartFile            file)            {            Cord            filename            =            StringUtils            .            cleanPath            (file.            getOriginalFilename            (            )            )            ;            try            {            if            (file.            isEmpty            (            )            )            {            throw            new            StorageException            (            "Failed to store empty file "            +            filename)            ;            }            if            (filename.            contains            (            ".."            )            )            {            // This is a security check            throw            new            StorageException            (            "Cannot store file with relative path outside current directory "            +            filename)            ;            }            try            (            InputStream            inputStream            =            file.            getInputStream            (            )            )            {            Files            .            copy            (inputStream,            this            .rootLocation.            resolve            (filename)            ,            StandardCopyOption            .REPLACE_EXISTING)            ;            }            }            take hold of            (            IOException            due east)            {            throw            new            StorageException            (            "Failed to store file "            +            filename,            due east)            ;            }            return            filename;            }            @Override            public            Stream                          <              Path              >                        loadAll            (            )            {            try            {            render            Files            .            walk            (            this            .rootLocation,            i            )            .            filter            (path            ->            !path.            equals            (            this            .rootLocation)            )            .            map            (            this            .rootLocation::            relativize            )            ;            }            take hold of            (            IOException            east)            {            throw            new            StorageException            (            "Failed to read stored files"            ,            e)            ;            }            }            @Override            public            Path            load            (            String            filename)            {            return            rootLocation.            resolve            (filename)            ;            }            @Override            public            Resource            loadAsResource            (            String            filename)            {            endeavor            {            Path            file            =            load            (filename)            ;            Resource            resources            =            new            UrlResource            (file.            toUri            (            )            )            ;            if            (resource.            exists            (            )            ||            resource.            isReadable            (            )            )            {            render            resource;            }            else            {            throw            new            FileNotFoundException            (            "Could not read file: "            +            filename)            ;            }            }            catch            (            MalformedURLException            e)            {            throw            new            FileNotFoundException            (            "Could non read file: "            +            filename,            e)            ;            }            }            @Override            public            void            deleteAll            (            )            {            FileSystemUtils            .            deleteRecursively            (rootLocation.            toFile            (            )            )            ;            }            }                  

The to a higher place implementation class is taken from Jump Kicking official files uploading example with few modifications done by me. The of import change I made is the improver of @PostConstruct note on the init() method. It guarantees that the init() method is just called once the bean is fully initialized with all the dependencies injected.

The FileSystemStorageService form throws exceptions in example of unexpected scenarios, for example, the file requested by the user might not exist.

The offset exception is StorageException which is thrown when we are unable to create the storage directory or the uploaded file is empty etc.

StorageException.java

                      packet            com.attacomsian.uploadfiles.storage            ;            public            class            StorageException            extends            RuntimeException            {            public            StorageException            (            String            message)            {            super            (message)            ;            }            public            StorageException            (            String            message,            Throwable            cause)            {            super            (message,            cause)            ;            }            }                  

The FileNotFoundException exception is thrown when a file is requested by the user but it does not exist on the server.

FileNotFoundException.java

                      package            com.attacomsian.uploadfiles.storage            ;            import            org.springframework.http.                        HttpStatus            ;            import            org.springframework.web.demark.annotation.                        ResponseStatus            ;            @ResponseStatus            (            HttpStatus            .NOT_FOUND)            public            form            FileNotFoundException            extends            StorageException            {            public            FileNotFoundException            (            String            bulletin)            {            super            (message)            ;            }            public            FileNotFoundException            (            Cord            message,            Throwable            cause)            {            super            (bulletin,            cause)            ;            }            }                  

Notice the @ResponseStatus(HttpStatus.NOT_FOUND) annotation in a higher place. This note ensures that Bound Kick responds with a 404 (Not Found) HTTP condition instead of 501 (Internal Server Fault) when the exception is thrown.

Running & Testing the Awarding

We are almost washed with our backend development. Since we created RESTful APIs for uploading and downloading files, nosotros tin can test them via Postman. Let's run the application by typing the following command in your terminal from the root directory of the project:

          $ ./gradlew bootRun                  

In one case the awarding is started, you can access it at http://localhost:8080.

1. Upload Single File

Single File Upload via REST API

2. Upload Multiple Files

Multiple Files Upload via REST API

iii. Download File

Download File via REST API

HTML Web Grade

Nosotros have tested our RESTful APIs and they are working fine. Now it is time to create a simple forepart-end interface using HTML & Thymeleaf that lists all the files uploaded so far. It will as well permit users to upload files directly from the browser.

listFiles.html

                                    <!              doctype              html              >                                                      <html              lang                              =                "en"                                            xmlns:thursday                              =                "http://www.thymeleaf.org"                            >                                                      <body              >                                                      <h1              >            Bound Boot File Upload Example                              </h1              >                                                      <60 minutes              />                                                      <h4              >            Upload Single File:                              </h4              >                                                      <form              method                              =                "Post"                            enctype                              =                "multipart/form-information"                                            th:activeness                              =                "@{/upload-file}"                            >                                                      <input              type                              =                "file"                            proper noun                              =                "file"                            >                                                      <br              />                                                      <br              />                                                      <push button              blazon                              =                "submit"                            >            Submit                              </push              >                                                      </form              >                                                      <hr              />                                                      <h4              >            Upload Multiple Files:                              </h4              >                                                      <form              method                              =                "Postal service"                            enctype                              =                "multipart/form-data"                                            th:action                              =                "@{/upload-multiple-files}"                            >                                                      <input              type                              =                "file"                            name                              =                "files"                            multiple              >                                                      <br              />                                                      <br              />                                                      <push button              blazon                              =                "submit"                            >            Submit                              </button              >                                                      </course              >                                                      <hr              />                                                      <h2              >            All Uploaded Files:                              </h2              >                                                      <ul              >                                                      <li                              th:each                              =                "file : ${files}"                            >                                                      <a                              th:href                              =                "${file}"                            target                              =                "_blank"                                            th:text                              =                "${file}"                            >                                                      </a              >                                                      </li              >                                                      </ul              >                                                      </body              >                                                      </html              >                              

The higher up template has two forms that enable users to upload a single file every bit well as multiple files. At the bottom, it also shows a list of currently uploaded files on the server. Here is how it looks like:

Spring Boot File Upload Web Page Example

Source code: Download the complete source lawmaking from GitHub available nether MIT license.

Conclusion

That's all folks for uploading and downloading files in Bound Boot. We discussed strategies for handling single also as multiple files via RESTful web services. We tested our REST APIs via Postman to confirm that they are working every bit expected. Finally, nosotros created the simplest web interface in HTML and Thymeleaf for showing a list of all the uploaded files.

In the end, I really appreciate that yous read this article and hope that you'd take learned how to handle files in Jump Boot today. If yous have any questions or feedback, delight experience free to transport me a tweet.

Happy learning Spring Boot 😍

Recommended for Yous ✌️

how to use Ajax to upload a file in Spring Kicking.

Uploading Files in Node.js and Limited

Uploading and Parsing CSV File using Spring Kick

✌️ Like this article? Follow me on Twitter and LinkedIn. You lot can also subscribe to RSS Feed.