Imagina que intentas rellenar un formulario en un sitio web, y el sistema te responde: "Error 400. Solicitud incorrecta. El campo user_input no cumple los requisitos de validación." ¿Notas ese tono frío y robotizado? Al usuario le cuesta entender qué falló y cómo solucionarlo.
Los mensajes de error bien redactados ayudan a:
- Guiar al usuario en la dirección correcta, indicando exactamente qué hizo mal.
- Reducir la frustración y aumentar la satisfacción con tu aplicación.
- Ayudar a los desarrolladores a encontrar problemas más rápido, usando el logging de errores.
Ahora que sabemos por qué es importante, manos a la obra.
Generación automática de mensajes con Bean Validation API
En las lecciones anteriores ya añadimos anotaciones para la validación de datos. Recordemos un ejemplo sencillo:
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class UserDto {
@NotNull(message = "El nombre de usuario no puede estar vacío")
@Size(min = 2, max = 30, message = "El nombre de usuario debe tener entre 2 y 30 caracteres")
private String username;
@NotNull(message = "El correo electrónico es obligatorio")
@Size(max = 50, message = "El correo electrónico no puede tener más de 50 caracteres")
private String email;
// Getters y setters
}
Aquí message pasa la cadena que se mostrará al usuario cuando se viole la regla. Es un buen punto de partida, pero podemos mejorarlo aún más.
Localización de mensajes usando MessageSource
Si desarrollas una aplicación para varios idiomas (por ejemplo, ruso e inglés), sería estupendo soportar la localización. Mira cómo se hace.
Configuración de MessageSource
Añadamos a nuestro proyecto una clase de configuración para la localización:
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
@Configuration
public class MessageConfig {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource =
new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages/validation");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
Este MessageSource leerá los archivos de localización. Por ejemplo, creemos el archivo validation.properties (por defecto para inglés) y validation_ru.properties para ruso:
validation.properties
username.notNull=Username is required
username.size=Username must be between 2 and 30 characters
email.notNull=Email is required
email.size=Email cannot exceed 50 characters
validation_ru.properties
username.notNull=El nombre de usuario no puede estar vacío
username.size=El nombre de usuario debe tener entre 2 y 30 caracteres
email.notNull=El correo electrónico es obligatorio
email.size=El correo electrónico no puede tener más de 50 caracteres
Modificar el DTO para usar mensajes localizados
Ahora podemos usar message con las claves de los archivos de localización:
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class UserDto {
@NotNull(message = "{username.notNull}")
@Size(min = 2, max = 30, message = "{username.size}")
private String username;
@NotNull(message = "{email.notNull}")
@Size(max = 50, message = "{email.size}")
private String email;
// Getters y setters
}
¡Listo! Ahora los mensajes se cargarán automáticamente según el idioma del usuario.
Validación y generación de mensajes en los controladores
Supongamos que tenemos un controlador para procesar la petición de creación de usuario:
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/users")
@Validated
public class UserController {
@PostMapping
public ResponseEntity<String> createUser(@Valid @RequestBody UserDto userDto) {
// Lógica para guardar el usuario...
return ResponseEntity.ok("¡Usuario creado con éxito!");
}
}
Si el usuario envía datos incorrectos, por ejemplo, un nombre vacío, Spring lanzará MethodArgumentNotValidException. Para manejarla y devolver un mensaje amigable, añadamos el siguiente manejador:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
Si el usuario envía la petición:
{
"username": "",
"email": "anInvalidEmail@verylongemailthatexceedsfiftycharacters.com"
}
La respuesta será un JSON estructurado y claro:
{
"username": "El nombre de usuario no puede estar vacío",
"email": "El correo electrónico no puede tener más de 50 caracteres"
}
Localización mediante cabeceras HTTP
Spring permite cambiar el idioma en las peticiones mediante la cabecera Accept-Language. Por ejemplo:
Accept-Language: en
En ese caso los mensajes de error se mostrarán en inglés. Si pones ru, los mensajes cambiarán automáticamente a ruso. Esto se hace con el LocaleResolver incorporado.
Añadamos un LocaleResolver:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
import java.util.Locale;
@Configuration
public class LocaleConfig {
@Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(Locale.ENGLISH);
return localeResolver;
}
}
Ahora el idioma se determinará automáticamente en función de la cabecera de la petición.
Consejos útiles
- Escribe mensajes claros. Evita abreviaturas y jerga técnica. El usuario debe entender qué tiene que hacer.
- Registra los errores. Si el usuario ve solo parte de la información, registra el resto en el servidor — eso ayudará a encontrar y arreglar problemas.
- Comprueba la localización. Asegúrate de que los mensajes se muestran correctamente en todos los idiomas soportados.
Ahora tenemos una herramienta potente para mostrar mensajes de error que sean comprensibles tanto para el usuario como para el desarrollador. Estos enfoques hacen que la aplicación sea más profesional y amigable. En la próxima clase veremos cómo profundizar más en la configuración de la localización de mensajes y cómo conectar el manejo de errores a nivel global.
GO TO FULL VERSION