Cómo hacer excepciones personalizadas en Java

    Visión general

    En este artículo, cubriremos el proceso de creación de excepciones personalizadas marcadas y no marcadas en Java.

    Si desea leer más sobre las excepciones y el manejo de excepciones en Java, lo cubrimos en detalle en: Manejo de excepciones en Java: una guía completa con las mejores y las peores prácticas

    ¿Por qué utilizar excepciones personalizadas?

    Aunque las excepciones de Java, tal como están, cubren casi todos los casos y condiciones excepcionales, su aplicación puede generar una excepción personalizada específica, única para su código y lógica.

    A veces, necesitamos crear las nuestras para representar excepciones de lógica empresarial, es decir, excepciones que son específicas de nuestra lógica empresarial o flujo de trabajo. Por ejemplo EmailNotUniqueException, InvalidUserStateExceptionetc.

    Ayudan a los clientes de la aplicación a comprender mejor qué salió mal. Son particularmente útiles para realizar el manejo de excepciones para las API REST, ya que diferentes restricciones de lógica empresarial requieren que se envíen diferentes códigos de respuesta al cliente.

    Si la definición de una excepción personalizada no proporciona ventajas sobre el uso de una excepción regular en Java, no es necesario definir excepciones personalizadas y debe limitarse a las que ya están disponibles, no es necesario inventar agua caliente nuevamente.

    Excepción de verificación personalizada

    Consideremos un escenario en el que queremos validar un correo electrónico que se pasa como argumento a un método.

    Queremos comprobar si es válido o no. Ahora podríamos usar la función integrada de Java IllegalArgumentException, lo cual está bien si solo estamos verificando una sola cosa, como si coincide con un REGEX predefinido o no.

    Pero supongamos que también tenemos una condición comercial para verificar que todos los correos electrónicos en nuestro sistema deben ser únicos. Ahora tenemos que realizar una segunda verificación (DB / llamada de red). Por supuesto, podemos usar el mismo IllegalArgumentException, pero no quedará claro cuál es la causa exacta: si el correo electrónico falló en la validación de REGEX o si el correo electrónico ya existe en la base de datos.

    Creemos una excepción personalizada para manejar esta situación. Para crear una excepción, como cualquier otra excepción, tenemos que extender el java.lang.Exception clase:

    public class EmailNotUniqueException extends Exception {
    
        public EmailNotUniqueException(String message) {
            super(message);
        }
    }
    
    

    Tenga en cuenta que proporcionamos un constructor que toma un String mensaje de error y llama al constructor de la clase principal. Ahora bien, esto no es obligatorio, pero es una práctica común tener algún tipo de detalles sobre la excepción que ocurrió.

    Llamando super(message), inicializamos el mensaje de error de la excepción y la clase base se encarga de configurar el mensaje personalizado, de acuerdo con el message.

    Ahora, usemos esta excepción personalizada en nuestro código. Como estamos definiendo un método que puede lanzar una excepción en la capa de servicio, lo marcaremos con el throws palabra clave.

    Si el correo electrónico de entrada ya existe en nuestra base de datos (en este caso una lista), throw nuestra excepción personalizada:

    public class RegistrationService {  
        List<String> registeredEmails = Arrays.asList("[email protected]", "[email protected]");
    
        public void validateEmail(String email) throws EmailNotUniqueException {
            if (registeredEmails.contains(email)) {
                throw new EmailNotUniqueException("Email Already Registered");
            }
        }
    }
    
    

    Ahora escribamos un cliente para nuestro servicio. Dado que es una excepción marcada, tenemos que cumplir con la regla de manejar o declarar. En el ejemplo anterior, decidimos «manejarlo»:

    public class RegistrationServiceClient {  
        public static void main(String[] args) {
            RegistrationService service = new RegistrationService();
            try {
                service.validateEmail("[email protected]");
            } catch (EmailNotUniqueException e) {
                // logging and handling the situation
            }
        }
    }
    
    

    Ejecutar este fragmento de código producirá:

    mynotes.custom.checked.exception.EmailNotUniqueException: Email Already Registered  
        at mynotes.custom.checked.exception.RegistrationService.validateEmail(RegistrationService.java:12)
        at mynotes.custom.checked.exception.RegistrationServiceClient.main(RegistrationServiceClient.java:9)
    
    

    Nota: El proceso de manejo de excepciones se omite por brevedad, pero es un proceso importante, no obstante.

    Excepción personalizada sin marcar

    Esto funciona perfectamente bien, pero nuestro código se ha vuelto un poco complicado. Además, estamos obligando al cliente a capturar nuestra excepción en un try-catch bloquear. En algunos casos, esto puede obligar a los desarrolladores a escribir código repetitivo.

    En este caso, puede resultar beneficioso crear una excepción de tiempo de ejecución personalizada. Para crear una excepción personalizada sin marcar, tenemos que extender el java.lang.RuntimeException clase.

    Consideremos la situación en la que tenemos que verificar si el correo electrónico tiene un nombre de dominio válido o no:

    public class DomainNotValidException extends RuntimeException {
    
        public DomainNotValidException(String message) {
            super(message);
        }
    }
    
    

    Ahora utilícelo en nuestro servicio:

    public class RegistrationService {
    
        public void validateEmail(String email) {
            if (!isDomainValid(email)) {
                throw new DomainNotValidException("Invalid domain");
            }
        }
    
        private boolean isDomainValid(String email) {
            List<String> validDomains = Arrays.asList("gmail.com", "yahoo.com", "outlook.com");
            if (validDomains.contains(email.substring(email.indexOf("@") + 1))) {
                return true;
            }
            return false;
        }
    }
    
    

    Tenga en cuenta que no tuvimos que usar el throws palabras clave en la firma del método ya que es una excepción sin marcar.

    Ahora escribamos un cliente para nuestro servicio. No tenemos que usar un try-catch bloquear esta vez:

    public class RegistrationServiceClient {
    
        public static void main(String[] args) {
            RegistrationService service = new RegistrationService();
            service.validateEmail("[email protected]");
        }
    }
    
    

    Ejecutar este fragmento de código producirá:

    Exception in thread "main" mynotes.custom.unchecked.exception.DomainNotValidException: Invalid domain  
        at mynotes.custom.unchecked.exception.RegistrationService.validateEmail(RegistrationService.java:10)
        at mynotes.custom.unchecked.exception.RegistrationServiceClient.main(RegistrationServiceClient.java:7)
    
    

    Nota: Por supuesto, puede rodear su código con un try-catch block para capturar la excepción que surge, pero ahora el compilador no lo fuerza.

    Relanzamiento de una excepción envuelta dentro de una excepción personalizada

    A veces necesitamos detectar una excepción y volver a lanzarla agregando algunos detalles más. Esto suele ser común si tiene varios códigos de error definidos en su aplicación que deben registrarse o devolverse al cliente en caso de esa excepción en particular.

    Suponga que su aplicación tiene un estándar ErrorCodes clase:

    public enum ErrorCodes {  
        VALIDATION_PARSE_ERROR(422);
    
        private int code;
    
        ErrorCodes(int code) {
            this.code = code;
        }
    
        public int getCode() {
            return code;
        }
    }
    
    

    Creemos nuestra excepción personalizada:

    public class InvalidCurrencyDataException extends RuntimeException {
    
        private Integer errorCode;
    
        public InvalidCurrencyDataException(String message) {
            super(message);
        }
    
        public InvalidCurrencyDataException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public InvalidCurrencyDataException(String message, Throwable cause, ErrorCodes errorCode) {
            super(message, cause);
            this.errorCode = errorCode.getCode();
        }
    
        public Integer getErrorCode() {
            return errorCode;
        }
    }
    
    

    Observe que tenemos varios constructores y deje que la clase de servicio decida cuál usar. Dado que estamos volviendo a generar la excepción, siempre es una buena práctica capturar la causa raíz de la excepción, por lo tanto, Throwable argumento que se puede pasar` al constructor de la clase padre.

    También estamos capturando el código de error en uno de los constructores y configuramos el errorCode dentro de la propia excepción. los errorCode puede ser utilizado por el cliente para el registro o cualquier otro propósito. Esto ayuda en un estándar más centralizado para el manejo de excepciones.

    Escribamos nuestra clase de servicio:

    public class CurrencyService {  
        public String convertDollarsToEuros(String value) {
            try {
            int x = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new InvalidCurrencyDataException("Invalid data", e, ErrorCodes.VALIDATION_PARSE_ERROR);
            }
            return value;
        }
    }
    
    

    Entonces atrapamos el estándar NumberFormatExceptiony tiramos el nuestro InvalidCurrencyDataException. Pasamos la excepción principal a nuestra excepción personalizada para que no perdamos la causa raíz por la que ocurrieron.

    Escribamos un cliente de prueba para este servicio:

    public class CurrencyClient {  
        public static void main(String[] args) {
            CurrencyService service = new CurrencyService();
        service.convertDollarsToEuros("asd");
        }
    }
    
    

    Salida:

    Exception in thread "main" mynotes.custom.unchecked.exception.InvalidCurrencyDataException: Invalid data  
        at mynotes.custom.unchecked.exception.CurrencyService.convertDollarsToEuro(CurrencyService.java:10)
        at mynotes.custom.unchecked.exception.CurrencyClient.main(CurrencyClient.java:8)
    Caused by: java.lang.NumberFormatException: For input string: "asd"  
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
        at java.lang.Integer.parseInt(Integer.java:580)
        at java.lang.Integer.parseInt(Integer.java:615)
        at mynotes.custom.unchecked.exception.CurrencyService.convertDollarsToEuro(CurrencyService.java:8)
        ... 1 more
    
    

    Como puede ver, tenemos un buen seguimiento de la pila de la excepción que podría ser útil para fines de depuración.

    Prácticas recomendadas para excepciones personalizadas

    • Adhiérase a la convención de nomenclatura general en todo el ecosistema de Java: todos los nombres de clase de excepción personalizados deben terminar con «Exception»
    • Evite hacer excepciones personalizadas si las excepciones estándar del propio JDK pueden cumplir el propósito. En la mayoría de los casos, no es necesario definir excepciones personalizadas.
    • Prefiera las excepciones en tiempo de ejecución a las excepciones marcadas. Frameworks como Spring han envuelto todas las excepciones marcadas en las excepciones en tiempo de ejecución, por lo que no han obligado al cliente a escribir código repetitivo que no quieren o no necesitan.
    • Proporcione muchos constructores sobrecargados en función de cómo se lanzaría la excepción personalizada. Si se está utilizando para volver a generar una excepción existente, definitivamente proporcione un constructor que establezca la causa.

    Conclusión

    Las excepciones personalizadas se utilizan para requisitos y lógica empresarial específicos. En este artículo, discutimos su necesidad y cubrimos su uso.

    El código de los ejemplos utilizados en este artículo se puede encontrar en Github.

    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *