Nestor Milyaev
Nestor Milyaev

Reputation: 6605

Java duplicated Exception handling in a separate method

I work on a Spring webStart application...

I have 2 (and potentially more) methods that process multi-tier Exception clause, as in:

...
    try {
        employeeService.updateEmployeePartner(employeeId, partner);
        LOG.info("partner details updated for partner id {}", employeeId);
        result = new ResponseEntity<>(partner.getId(), HttpStatus.OK);
    } catch (EmployeePartnerNotFoundException ex) {
        LOG.error(ex.getMessage() + " employee id: ", employeeId);
        errorResponse = new ErrorResponse("500", ex.getMessage());
    } catch (ReadOperationDeniedException ex) {
        LOG.error("User doesn't have permissions to update employee's {} details: {}", employeeId, ex.getMessage());
        errorResponse = new ErrorResponse("403", "User doesn't have permissions to update employee's details");
    } catch (Exception ex) {
        LOG.error("something went wrong while updating employee's {} partner details: {}", employeeId, ex.getMessage());
        errorResponse = new ErrorResponse("500", "unspecified server error");
    } finally {
        result = (result != null) ? result : new ResponseEntity<>(errorResponse, HttpStatus.I_AM_A_TEAPOT); // should be INTERNAL_SERVER_ERROR
    }
...

Another method is almost identical, apart for this change: employeeService.updateEmployeePartner(employeeId, partner); => employeeService.createEmployeePartner(employeeId, partner); and catching EmployeePartnerAlreadyExistsException in that block.

Now, to reduce code duplication, I want to group all Error handling code in one place (method), so I replaced the above code with the following

...
        try {
            employeeService.updateEmployeePartner(employeeId, partner);
            LOG.info("partner details updated for partner id {}", employeeId);
            result = new ResponseEntity<>(partner.getId(), HttpStatus.OK);
        } catch (Exception ex) {
            errorResponse = processException(ex, employeeId, "update");
        } finally {
            result = (result != null) ? result : new ResponseEntity<>(errorResponse, HttpStatus.I_AM_A_TEAPOT); // should be INTERNAL_SERVER_ERROR
        }
...
    private ErrorResponse processException(Exception ex, Long employeeId, String operation) {
        ErrorResponse errorResponse;
        if (ex.getClass().equals(EmployeePartnerNotFoundException.class) ||
                ex.getClass().equals(EmployeePartnerExistsException.class)) {
            LOG.error(ex.getMessage() + " employee id: ", employeeId);
            errorResponse = new ErrorResponse("500", ex.getMessage());
        } else if (ex.getClass().isInstance(ReadOperationDeniedException.class)) {
            LOG.error("User doesn't have permissions to " + operation + " employee's {} details: {}", employeeId, ex.getMessage());
            errorResponse = new ErrorResponse("403", "User doesn't have permissions to " + operation + " employee's details");
        } else { // Exception
            LOG.error("something went wrong while trying to " + operation + "  employee's {} partner details: {}", employeeId, ex.getMessage());
            errorResponse = new ErrorResponse("500", "unspecified server error");
        }
        return errorResponse;
    }

Is that a good enough approach or are there any patterns to handle exceptions in the above scenario by outsourcing the handling to a separate method/class?

Since it's a spring application, I also consider using Spring exception handlers, as in:

 @ExceptionHandler(Exception.class)

, but that will only cover part of my requirements.

Upvotes: 1

Views: 1535

Answers (2)

Nestor Milyaev
Nestor Milyaev

Reputation: 6605

That's what I ended up doing in the end, along with the reply by Sangam:

Individual Exception handlers worked quite well; Do note there is no need to put these in separate class.

But I still wonder if there is a similar pattern where the application is not Spring MVC?

    public ResponseEntity<?> updatePartnerDetails(@PathVariable("employeeId") Long employeeId,
                                                  @RequestBody PersonDetails partnerDto) {
        LOG.info("Updating partner details for employee {}, partner details {}", employeeId, partnerDto);
        validateRequestValues(partnerDto);
        // Try-catches were around this call
        Person partner = PersonMapper.fromPersonDetails(partnerDto);
        employeeService.updateEmployeePartner(employeeId, partner);
        LOG.info("partner details updated for partner id {}", employeeId);
        return new ResponseEntity<>(partner.getId(), HttpStatus.OK);
    }

    @ResponseStatus(HttpStatus.I_AM_A_TEAPOT)  // TODO: BAD_REQUEST
    @ExceptionHandler({EmployeePartnerExistsException.class, EmployeePartnerNotFoundException.class})
    public ResponseEntity<?> employeePartnerError(Exception ex) {
        LOG.error(ex.getMessage());
        return new ResponseEntity<Object>(new ErrorResponse(400, ex.getMessage()), HttpStatus.OK);
    }

    @ResponseStatus(HttpStatus.I_AM_A_TEAPOT)  // TODO: BAD_REQUEST
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<?> validationError(Exception ex) {
        LOG.error(ex.getMessage());
        return new ResponseEntity<Object>(new ErrorResponse(400, ex.getMessage()), HttpStatus.OK);
    }

    @ResponseStatus(HttpStatus.I_AM_A_TEAPOT)  // TODO: FORBIDDEN
    @ExceptionHandler(ReadOperationDeniedException.class)
    public ResponseEntity<?> forbidden(Exception ex) {
        LOG.error("User doesn't have permissions to amend employee's details");
        return new ResponseEntity<Object>(new ErrorResponse(403, "User doesn't have permissions to amend employee's details"), HttpStatus.OK);
    }

    @ResponseStatus(HttpStatus.I_AM_A_TEAPOT)  // TODO: INTERNAL_SERVER_ERROR
    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> unspecifiedError(Exception ex) {
        LOG.error("User doesn't have permissions to amend employee's details");
        return new ResponseEntity<Object>(new ErrorResponse(500, "Something went wrong while editing employee's details"), HttpStatus.OK);
    }

Upvotes: 0

Sangam Belose
Sangam Belose

Reputation: 4506

Use @ControllerAdvice with your custom ErrorResponse and each Handler for seprate exceptions. Refer Custom error response Spring

Sample code:

    @ControllerAdvice
    public class GlobalExceptionHandlers {

        private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandlers.class);


        /***************** User Defined Exceptions *************************/

        @ExceptionHandler({ EmployeePartnerNotFoundException.class })
        public ResponseEntity<Object> handleEmployeePartnerNotFoundException(EmployeePartnerNotFoundException ex) {

        logger.error("EmployeePartnerNotFoundException : ", ex);

        ErrorResponse errorResponse = new ErrorResponse("500", ex.getMessage());

        return new ResponseEntity<Object>(errorResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST);
        }

        // other exception handlers

}

Upvotes: 1

Related Questions