Kotlin SpringBoot Upload/Download File – MultipartFile + Thymeleaf + Bootstrap 4 example

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:

Kotlin-SpringBoot-Upload-Download-File-MultipartFile-Thymeleaf-Bootstrap-4-project-structure

-> Upload Form:

Kotlin-SpringBoot-Upload-Download-File-MultipartFile-Thymeleaf-Bootstrap-4-upload-view

-> Download Form:

Kotlin-SpringBoot-Upload-Download-File-MultipartFile-Thymeleaf-Bootstrap-4-download-files

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

Kotlin-SpringBoot-Upload-Download-File-MultipartFile-Thymeleaf-Bootstrap-4-upload-view

-> Browser Network logs:

Kotlin-SpringBoot-Upload-Download-File-MultipartFile-Thymeleaf-Bootstrap-4-upload-request

-> Stored Files:

Kotlin-SpringBoot-Upload-Download-File-MultipartFile-Thymeleaf-Bootstrap-4-result-uploadedfile

Retrieve Files

Kotlin-SpringBoot-Upload-Download-File-MultipartFile-Thymeleaf-Bootstrap-4-retrieve-files

Download Files

Kotlin-SpringBoot-Upload-Download-File-MultipartFile-Thymeleaf-Bootstrap-4-download-files

-> Browser Network logs:

Kotlin-SpringBoot-Upload-Download-File-MultipartFile-Thymeleaf-Bootstrap-4-download-file-logs

SourceCode

KotlinSpringBootUploadDownloadFiles

0 0 votes
Article Rating
Subscribe
Notify of
guest
232 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments