Error Handling Best Practices in Spring Boot with Kotlin
Master Spring Boot error handling in Kotlin with comprehensive examples: learn to implement global exception handlers, custom error responses, and production-ready error handling strategies
In this tutorial, we'll create a complete file upload and download system using Spring Boot and Kotlin. We'll cover:
First, ensure you have these dependencies in your build.gradle.kts
:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}
First, let's create a configuration class for file storage properties:
@Configuration
@ConfigurationProperties(prefix = "file")
class FileStorageProperties {
lateinit var uploadDir: String
}
Add this to your application.properties
:
file.upload-dir=./uploads
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
Create a service to handle file operations:
@Service
class FileStorageService(
private val fileStorageProperties: FileStorageProperties
) {
private val root: Path
init {
root = Paths.get(fileStorageProperties.uploadDir)
.toAbsolutePath()
.normalize()
Files.createDirectories(root)
}
fun storeFile(file: MultipartFile): String {
// Normalize file name
val fileName = StringUtils.cleanPath(file.originalFilename ?: throw IllegalArgumentException("Original filename must not be null"))
try {
// Check if the file name contains invalid characters
if (fileName.contains("..")) {
throw StorageException("Sorry! Filename contains invalid path sequence $fileName")
}
// Copy file to the target location
val targetLocation = root.resolve(fileName)
Files.copy(file.inputStream, targetLocation, StandardCopyOption.REPLACE_EXISTING)
return fileName
} catch (ex: IOException) {
throw StorageException("Could not store file $fileName. Please try again!", ex)
}
}
fun loadFileAsResource(fileName: String): Resource {
try {
val filePath = root.resolve(fileName).normalize()
val resource = UrlResource(filePath.toUri())
if (resource.exists()) {
return resource
} else {
throw FileNotFoundException("File not found $fileName")
}
} catch (ex: MalformedURLException) {
throw FileNotFoundException("File not found $fileName")
}
}
}
Create custom exceptions:
class StorageException : RuntimeException {
constructor(message: String) : super(message)
constructor(message: String, cause: Throwable) : super(message, cause)
}
class FileNotFoundException(message: String) : RuntimeException(message)
Create a controller to handle upload and download requests:
@RestController
@RequestMapping("/api/files")
class FileController(
private val fileStorageService: FileStorageService
) {
@PostMapping("/upload")
fun uploadFile(@RequestParam("file") file: MultipartFile): UploadFileResponse {
val fileName = fileStorageService.storeFile(file)
val fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/api/files/download/")
.path(fileName)
.toUriString()
return UploadFileResponse(
fileName = fileName,
fileDownloadUri = fileDownloadUri,
fileType = file.contentType ?: "unknown",
size = file.size
)
}
@PostMapping("/upload-multiple")
fun uploadMultipleFiles(@RequestParam("files") files: Array<MultipartFile>): List<UploadFileResponse> {
return files.map { file -> uploadFile(file) }
}
@GetMapping("/download/{fileName:.+}")
fun downloadFile(@PathVariable fileName: String, request: HttpServletRequest): ResponseEntity<Resource> {
val resource = fileStorageService.loadFileAsResource(fileName)
var contentType = request.servletContext.getMimeType(resource.file.absolutePath)
if (contentType == null) {
contentType = "application/octet-stream"
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"${resource.filename}\"")
.body(resource)
}
}
Create data classes for responses:
data class UploadFileResponse(
val fileName: String,
val fileDownloadUri: String,
val fileType: String,
val size: Long
)
Add a global exception handler:
@ControllerAdvice
class FileUploadExceptionHandler {
@ExceptionHandler(StorageException::class)
fun handleStorageException(ex: StorageException): ResponseEntity<ErrorResponse> {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ErrorResponse("Storage error", ex.message))
}
@ExceptionHandler(FileNotFoundException::class)
fun handleFileNotFoundException(ex: FileNotFoundException): ResponseEntity<ErrorResponse> {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ErrorResponse("File not found", ex.message))
}
data class ErrorResponse(
val error: String,
val message: String?
)
}
Here's how to use the file upload/download endpoints:
curl -X POST \
http://localhost:8080/api/files/upload \
-H 'content-type: multipart/form-data' \
-F 'file=@/path/to/your/file.pdf'
curl -X POST \
http://localhost:8080/api/files/upload-multiple \
-H 'content-type: multipart/form-data' \
-F 'files=@/path/to/file1.pdf' \
-F 'files=@/path/to/file2.jpg'
curl -X GET \
http://localhost:8080/api/files/download/filename.pdf \
-O -J
Here's a basic test for the FileStorageService:
@SpringBootTest
class FileStorageServiceTest {
@Autowired
private lateinit var fileStorageService: FileStorageService
@Test
fun `when storing file then file is saved successfully`() {
// Create a mock MultipartFile
val content = "test content".toByteArray()
val file = MockMultipartFile(
"test.txt",
"test.txt",
MediaType.TEXT_PLAIN_VALUE,
content
)
// Store the file
val fileName = fileStorageService.storeFile(file)
// Verify the file exists
val savedFile = fileStorageService.loadFileAsResource(fileName)
assertTrue(savedFile.exists())
assertEquals("test.txt", savedFile.filename)
}
}
Master Spring Boot error handling in Kotlin with comprehensive examples: learn to implement global exception handlers, custom error responses, and production-ready error handling strategies
Master Role-Based Access Control (RBAC) in Spring Boot applications using Kotlin with practical examples, from basic setup to advanced configurations with method-level security
Learn how to implement secure API key authentication in Spring Boot with Kotlin, including rate limiting, monitoring, and production-ready best practices - complete with practical examples and ready-to-use code