In the tutorial, we will show you how to build a SpringBoot web-application to upload/download file with Thymeleaf engine and Bootstrap 4 using Kotlin language.
Technologies
- Java 8
- Maven 3.6.1
- Spring Tool Suite: Version 3.9.4.RELEASE
- Spring Boot: 2.0.2.RELEASE
- Bootstrap 4
- Kotlin – Version: 1.2.41
Goal
We create a SpringBoot project as below:
-> Upload Form:
-> Download Form:
Practice
We create a SpringBoot project with below dependencies:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-kotlin</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jdk8</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-reflect</artifactId> </dependency>
Frontend
Upload Multipart-Form
– Create /templates/multipartfile/uploadform.html file:
<!DOCTYPE html> <html lang="en"> <head> <title>Upload MultipartFile</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.1.0/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script> </head> <body> <div class="container h-100"> <div class="h-100"> <div class="row h-100 justify-content-center align-items-center"> <div class="col-sm-5"> <h3>Upload MultipartFile to FileSystem</h3> <form method="POST" enctype="multipart/form-data" id="fileUploadForm"> <div class="form-group"> <label class="control-label" for="uploadfile">Upload File:</label> <input type="file" class="form-control" id="uploadfile" placeholder="Upload File" name="uploadfile"></input> </div> <button type="submit" class="btn btn-default" id="btnSubmit">Upload</button> <a href="/files" class="btn btn-default" role="button">Files</a> </form> <div th:if="${message}"> <span th:text="${message}"/> </div> </div> </div> </div> </div> </body> </html>
Retrieves Files
– Create /templates/multipartfile/listfiles.html file:
<!DOCTYPE html> <html lang="en"> <head> <title>Upload MultipartFile</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.1.0/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script> </head> <body> <div class="container h-100"> <div class="row h-100 justify-content-center align-items-center"> <div class="col-md-7 table-responsive"> <h2>Uploaded Files</h2> <table id="customerTable" class="table"> <thead> <tr> <th>No</th> <th>Filename</th> <th>Download</th> </tr> </thead> <tbody> <tr th:each="file, rowStat: ${files}"> <td th:text="${rowStat.count}">1</td> <td th:text="${file.filename}">File-Name</td> <td><a th:href="${file.url}">Link</a></td> </tr> </tbody> </table> <div> <a href="/" class="btn btn-light btn-block" role="button">Back to Upload Form</a> </div> </div> </div> </div> </body> </html>
Kotlin Backend
File Storage
– Create Kotlin interface FileStorage.kt file:
package com.javasampleapproach.kotlin.uploadfile.filestorage import java.nio.file.Path; import java.util.stream.Stream; import org.springframework.core.io.Resource; import org.springframework.web.multipart.MultipartFile; interface FileStorage { fun store(file: MultipartFile) fun loadFile(filename: String): Resource fun deleteAll() fun init() fun loadFiles(): Stream}
– Implement above interface by class FileStorageImpl.kt:
package com.javasampleapproach.kotlin.uploadfile.filestorage 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; @Service class FileStorageImpl: FileStorage{ val log = LoggerFactory.getLogger(this::class.java) val rootLocation = Paths.get("filestorage") override fun store(file: MultipartFile){ Files.copy(file.getInputStream(), this.rootLocation.resolve(file.getOriginalFilename())) } override fun loadFile(filename: String): Resource{ val file = rootLocation.resolve(filename) val resource = UrlResource(file.toUri()) if(resource.exists() || resource.isReadable()) { return resource }else{ throw RuntimeException("FAIL!") } } override fun deleteAll(){ FileSystemUtils.deleteRecursively(rootLocation.toFile()) } override fun init(){ Files.createDirectory(rootLocation) } override fun loadFiles(): Stream{ return Files.walk(this.rootLocation, 1) .filter{path -> !path.equals(this.rootLocation)} .map(this.rootLocation::relativize) } }
Upload/Download File Controller
– Firstly, create a simple data model FileInfo.kt that contains information of a file:
package com.javasampleapproach.kotlin.uploadfile.model class FileInfo( val filename: String = "", val url: String = "" )
– Implement controller UploadFileController.kt:
package com.javasampleapproach.kotlin.uploadfile.controller import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import com.javasampleapproach.kotlin.uploadfile.filestorage.FileStorage; @Controller class UploadFileController { @Autowired lateinit var fileStorage: FileStorage @GetMapping("/") fun index(): String { return "multipartfile/uploadform.html" } @PostMapping("/") fun uploadMultipartFile(@RequestParam("uploadfile") file: MultipartFile, model: Model): String { fileStorage.store(file); model.addAttribute("message", "File uploaded successfully! -> filename = " + file.getOriginalFilename()) return "multipartfile/uploadform.html" } }
– Implement controller DownloadFileController.kt:
package com.javasampleapproach.kotlin.uploadfile.controller 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.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import com.javasampleapproach.kotlin.uploadfile.filestorage.FileStorage; import com.javasampleapproach.kotlin.uploadfile.model.FileInfo; @Controller class DownloadFileController { @Autowired lateinit var fileStorage: FileStorage /* * Retrieve Files' Information */ @GetMapping("/files") fun getListFiles(model: Model): String { val fileInfos: List= fileStorage.loadFiles().map{ path -> FileInfo(path.getFileName().toString(), MvcUriComponentsBuilder.fromMethodName(DownloadFileController::class.java, "downloadFile", path.getFileName().toString()).build().toString()) } .collect(Collectors.toList()) model.addAttribute("files", fileInfos) return "multipartfile/listfiles" } /* * Download Files */ @GetMapping("/files/{filename}") fun downloadFile(@PathVariable filename: String): ResponseEntity { val file = fileStorage.loadFile(filename) return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"") .body(file); } }
Note:
We can configure multipart.max-file
in application.properties to limit the size of uploaded file:
spring.http.multipart.max-file-size=500KB spring.http.multipart.max-request-size=500KB
Main Class
Init storage folder in main class KotlinSpringBootUploadDownloadFilesApplication.kt:
package com.javasampleapproach.kotlin.uploadfile import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.boot.CommandLineRunner import org.springframework.boot.SpringApplication import org.springframework.context.annotation.Bean import com.javasampleapproach.kotlin.uploadfile.filestorage.FileStorage; @SpringBootApplication class KotlinSpringBootUploadDownloadFilesApplication{ @Autowired lateinit var fileStorage: FileStorage; @Bean fun run() = CommandLineRunner { fileStorage.deleteAll() fileStorage.init() } } fun main(args: Array) { //runApplication (*args) SpringApplication.run(KotlinSpringBootUploadDownloadFilesApplication::class.java, *args) }
Run & Check Results
Run the SpringBoot project, then makes upload/download requests ->
Upload File
-> Browser Network logs:
-> Stored Files:
Retrieve Files
Download Files
-> Browser Network logs:
SourceCode
KotlinSpringBootUploadDownloadFiles