1. Introducción
En este breve tutorial, aprenderemos cómo subir un archivo utilizando Spring Boot.
Veamos cómo hacerlo a través de un form submission.
2. ¿Por qué son importantes las cargas de archivos?
Subir archivos es un caso de uso muy común en las aplicaciones web modernas, por ejemplo, adjuntando documentos en formularios o subiendo fotos de perfil. Aunque es sencillo de entender, manejar archivos correctamente presenta algunos desafíos. Por ejemplo, exponer una aplicación sin restricciones sobre el tamaño de archivo que los usuarios pueden subir puede abrumar nuestra aplicación o consumir todo el almacenamiento disponible. Por lo tanto, antes de aceptar cualquier archivo, necesitamos validar su tipo y contenido y manejar los errores de manera adecuada.
Entonces, en nuestra aplicación, utilizaremos las opciones ofrecidas por Spring Boot para hacerla robusta, imponiendo un límite en el tamaño del archivo y proporcionando retroalimentación para que el usuario entienda lo que está sucediendo.
3. Flujo de Carga
El siguiente diagrama de secuencia muestra visualmente las interacciones entre el Usuario, el Navegador y nuestra aplicación de Spring Boot:
Lo siento, pero parece que no has incluido contenido para traducir. Si deseas que traduzca algo específico, por favor proporciónamelo y estaré encantado de ayudarte.
4. Configuración de Maven
Comencemos incluyendo nuestras dependencias. Asegurémonos de obtener las versiones más recientes de spring-boot-starter-web y spring-boot-starter-thymeleaf.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
Para un nuevo proyecto, es mejor empezar desde el Spring Initializr e incluir estas dependencias:
Spring Web
: marco MVC y Tomcat embebidoThymeleaf
: Un moderno motor de plantillas Java del lado del servidor para entornos web y autónomos.
5. Implementando la Carga de Archivos
Comencemos a implementar nuestra aplicación.
5.1 Formulario de Carga en Thymeleaf
Primero, crearemos una plantilla de Thymeleaf que incluya un formulario básico con un campo de entrada de archivo y un botón para enviar el formulario a nuestro endpoint.
Necesitamos crear un archivo file-upload.html
en una carpeta llamada templates
dentro de la carpeta de recursos de la aplicación:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mini.css/3.0.1/mini-default.min.css">
</head>
<body>
<div class="container">
<header>
<h2>File Upload</h2>
</header>
<main>
<form action="#" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="input-group vertical">
<input type="file" name="file" required>
<button type="submit">Upload</button>
</div>
</form>
<div th:if="${message}" class="alert"
th:classappend="${message.contains('Success') ? 'alert-success' : 'alert-error'}"
th:text="${message}"></div>
</main>
</div>
</body>
</html>
enctype=multipart/form-data
. Sin él, el navegador no enviará el archivo como datos multipart, y la carga no funcionará como se espera.Después del form, agregamos un placeholder para mostrar mensajes.
En nuestra plantilla, estamos utilizando mini.css para que no tengamos que preocuparnos por el estilo de la aplicación, pero aún así evitar la apariencia predeterminada del navegador.
Eso es todo para el lado del front-end.
5.2. Configuración de Almacenamiento
A continuación, necesitamos decidir dónde guardar los archivos que nuestros usuarios suben. Añadamos una propiedad files.upload-dir
en el archivo application.properties
:
server.port=8090
files.upload-dir=./uploadedfiles
Este será el lugar donde guardaremos los archivos.
5.3 Controlador de Carga de Archivos
Ahora, implementaremos un controlador llamado FileUploadController
que expone un endpoint para servir la plantilla que creamos al navegador:
@Controller
public class FileUploadController {
@GetMapping("/")
public String fileUpload() {
return "file-upload";
}
}
Solo necesitamos asegurarnos de que la cadena que devolvemos coincida con el nombre de la plantilla que creamos anteriormente.
A continuación, en el mismo controlador, crearemos un endpoint POST /upload
para manejar las solicitudes de carga de archivos. Este endpoint utilizará un MultipartFile
como parámetro de solicitud para obtener el archivo. MultipartFile
es una interfaz que extiende InputStreamSource
:
@Value("${files.upload-dir}")
private String uploadDir;
@PostMapping("/upload")
public String handleUpload(
@RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes
) {
if (file.isEmpty()) {
redirectAttributes.addFlashAttribute("message", "Please select a file");
return "redirect:/";
}
try {
String filename = StringUtils.cleanPath(file.getOriginalFilename());
Path destination = Paths.get(uploadDir, filename);
Files.createDirectories(destination.getParent());
file.transferTo(destination);
redirectAttributes.addFlashAttribute("message", "Success: " + filename + " (" + file.getSize() + " bytes)");
return "redirect:/";
} catch (IOException e) {
redirectAttributes.addFlashAttribute("message", "Error: " + e.getMessage());
return "redirect:/";
}
}
En resumen, primero obtenemos el nombre del archivo del objeto de entrada file
. Luego, creamos las carpetas necesarias para guardar destination
usando StringUtils.cleanPath
para prevenir ataques de traversal de ruta simple. Después, guardamos los datos del archivo entrante en destination
y enviamos un mensaje de éxito. En caso de falla, notificamos al usuario en consecuencia para que sepa lo que está sucediendo.
También, en el controlador, inyectamos el directorio de carga usando @Value
e implementamos un método @PostMapping
que recibe el MultipartFile
junto con un objeto RedirectAttributes
. La interfaz RedirectAttributes
en Spring MVC se utiliza para pasar datos durante una redirección. Específicamente, usamos addFlashAttribute
para guardar el mensaje en la sesión temporalmente, de modo que esté disponible después de la redirección cuando se procesa la plantilla, permitiendo así mostrar la retroalimentación correcta.
StringUtils.cleanPath
no debe ser utilizado como referencia en un contexto de seguridad. Se deben emplear otros mecanismos para evitar problemas de recorrido de ruta.Ahora, si ejecutamos la aplicación usando mvn spring-boot:run
y vamos a localhost:8090
, deberíamos ver nuestro form y poder subir archivos pequeños.
6. Manejo de Archivos Grandes
Ahora podemos subir archivos pequeños sin ningún problema. Pero si el tamaño del archivo es mayor a 1MB (el límite predeterminado), nuestra aplicación fallará, y el navegador podría mostrar un error. Si queremos permitir a los usuarios subir archivos más grandes, podemos configurar un umbral más alto.
6.1 Configurar el Tamaño Máximo de Archivo
Para configurar el tamaño máximo de archivo permitido, cambiamos las propiedades spring.servlet.multipart.max-file-size
y spring.servlet.multipart.max-request-size
en application.properties
:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=11MB
Esto por sí solo no es suficiente. También necesitamos configurar nuestro contenedor de servlets para que no aborte una conexión si la solicitud entrante excede su valor máximo de tamaño predeterminado antes de que se active el manejo de multipart de Spring. Nuestra aplicación de Spring Boot utiliza Tomcat embebido por defecto. Podemos configurarlo para aceptar solicitudes POST más grandes (o de tamaño efectivamente ilimitado) de modo que las verificaciones de tamaño sean manejadas únicamente por la configuración de multipart de Spring:
server.tomcat.max-http-form-post-size=-1
server.tomcat.max-swallow-size=-1
Ahora, si intentamos subir archivos que superen los 10MB, Spring MVC impedirá la carga, pero el comportamiento por defecto todavía podría resultar en una página de error no manejada (como una página de error Whitelabel mostrando un error 500 relacionado con MaxUploadSizeExceededException
). Si queremos personalizar este comportamiento para que la interacción en la interfaz de usuario sea más fluida, podemos agregar manejo de errores personalizado.
6.2 Manejo de Errores Personalizado
Detrás de escena, Spring MVC lanza una MaxUploadSizeExceededException
cuando un archivo excede el límite de tamaño configurado. Podemos crear un @ControllerAdvice
para capturar MultipartException
globalmente y manejar estos errores de manera elegante. Capturamos MultipartException
porque MaxUploadSizeExceededException
la extiende, lo que nos permite manejar otros errores potenciales relacionados con multipart también:
@ControllerAdvice
public class FileUploadExceptionHandler {
@ExceptionHandler(MultipartException.class)
public String handleSizeExceeded(RedirectAttributes attrs) {
attrs.addFlashAttribute("message",
"File exceeds size limit!");
return "redirect:/";
}
}
Configurar un @ControllerAdvice
proporciona una manera centralizada de manejar excepciones a través de múltiples controladores y devolver mensajes de error amigables para el usuario o redirigir con la retroalimentación adecuada.
7. Consejos para Entornos de Producción
Mientras que este tutorial explica una carga de archivos simple, hay varias consideraciones adicionales para entornos de producción que no se abordan aquí.
Primero, no estamos limitando el tipo de archivos permitidos para subir. Podríamos resolver esto con una simple verificación de la extensión del archivo o implementar una validación más profunda, como verificar el tipo MIME o escanear en busca de virus y malware.
En entornos de producción donde se esperan archivos grandes, consideremos utilizar un enfoque de streaming para evitar cargar el archivo completo en la memoria del servidor. Alternativamente, consideremos utilizar cargas en bloques desde el lado del cliente, donde el archivo se divide en partes más pequeñas, se sube por separado y luego se vuelve a ensamblar en el lado del servidor.
8. Conclusión
En este artículo, aprendimos cómo implementar la carga de archivos en Spring Boot utilizando un formulario HTML simple, Thymeleaf y un controlador Spring MVC. También aprendimos cómo configurar límites para archivos grandes y personalizar el proceso de manejo de errores para las excepciones de tamaño.
Puedes encontrar el código completo en GitHub.