Helen Reeves
Helen Reeves

Reputation: 469

How catch hibernate/jpa constraint violations in Spring Boot?

I have been unable to catch ConstraintViolationException (or DataIntegrityViolationException) in ResponseEntityExceptionHandler. I would like to return the jpa failure (e.g. which constraint violated) in the response. (I prefer not to use @Valid on the method parameter and catch handleMethodArgumentNotValid).

...
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.dao.DataIntegrityViolationException;

@ControllerAdvice
public class PstExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler({ConstraintViolationException.class})
    public ResponseEntity<Object> handleConstraintViolation(
        ConstraintViolationException ex, WebRequest request) {

        ...

        return new ResponseEntity<Object>(...);
    }
}

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
public class Student {

    @Id
    private Long id;

    @NotNull
    private String name;

    @NotNull
    @Size(min=7, message="Passport should have at least 7 characters")
    private String passportNumber;

    public Student() {
    }

...

}

@RequestMapping(value = "/addstudent"...)
@ResponseBody
public ResponseEntity<Object> addStudent(@RequestBody StudentDto studentDto) {

    Student student = new Student();
    student.setId(studentDto.getId());                          // 1L
    student.setName(studentDto.getName());                      // "helen"
    student.setPassportNumber(studentDto.getPassportNumber());  // "321"

    studentRepository.save(student);

    return ResponseEntity.accepted().body(student);
}

thank you...

Upvotes: 3

Views: 15148

Answers (6)

Pranav Barot
Pranav Barot

Reputation: 59

In my case, I use the @Valid annotation in the Controller class but not in the Service class.

In my repository, I pass the method throw the service class and after passing it to the controller.

So if you pass method throw service you also have to pass @Valid annotation in the Service class

Upvotes: 0

tyro
tyro

Reputation: 617

Something like this would help:

@ExceptionHandler(value = {DataIntegrityViolationException.class})
public ResponseEntity<String> handlePreconditionFailed(DataIntegrityViolationException exception) {
        LOG.error(ERROR_MESSAGE, exception);
        return ResponseEntity.status(HttpStatus.CONFLICT).build();
}

Upvotes: 0

I had the same issue so I had to catch the JDBCException exception:

@ExceptionHandler({JDBCException.class})
public ResponseEntity<Object> handleConstraintViolation(TransactionSystemException ex, WebRequest request) {

    if (ex.getCause() instanceof ConstraintViolationException) {
        ...
    }   

    return new ResponseEntity<Object>(...);
}

Upvotes: 0

Saeid Babaei
Saeid Babaei

Reputation: 86

You cannot catch ConstraintViolationException.class because it's not propagated to that layer of your code, it's caught by the lower layers, wrapped and rethrown under another type. So that the exception that hits your web layer is not a ConstraintViolationException. So you could do something like this:

@ExceptionHandler({TransactionSystemException.class})
protected ResponseEntity<Object> handlePersistenceException(final Exception ex, final WebRequest request) {
    logger.info(ex.getClass().getName());
    //
    Throwable cause = ((TransactionSystemException) ex).getRootCause();
    if (cause instanceof ConstraintViolationException) {        

        ConstraintViolationException consEx= (ConstraintViolationException) cause;
        final List<String> errors = new ArrayList<String>();
        for (final ConstraintViolation<?> violation : consEx.getConstraintViolations()) {
            errors.add(violation.getPropertyPath() + ": " + violation.getMessage());
        }

        final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, consEx.getLocalizedMessage(), errors);
        return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
    }
    final ApiError apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage(), "error occurred");
    return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
}

Upvotes: 0

WildDev
WildDev

Reputation: 2367

There're 3 ways:

  1. @Valid \ @Validated annotations.

    Once they used at parameter level, violations are available via method-injected Errors \ BindingResult type implementations.

  2. ConstraintViolationException is thrown each time an entity in non-valid state is passed to .save() or .persist() Hibernate methods.

  3. Entities may be manually validated via injected LocalValidatorFactoryBean.validate() method. The constraint violations then available through passed Errors object's implementation. Exactly, how @Valid \ @Validated annotations are internally implemented.

Upvotes: 0

Jonathan JOhx
Jonathan JOhx

Reputation: 5968

Exceptions like ConstraintViolationException are not taken in account because they are extend from HibernateException. you can take a look exceptions in convert method that those are wrapped on Hibernate Exceptions.

@ExceptionHandler({TransactionSystemException.class})
public ResponseEntity<Object> handleConstraintViolation(TransactionSystemException ex, WebRequest request) {

    if (ex.getCause() instanceof RollbackException) {
       RollbackException rollbackException = (RollbackException) ex.getCause();
         if (rollbackException.getCause() instanceof ConstraintViolationException) {
             return new ResponseEntity<Object>(...);
         }
    ...
    }
    ...

    return new ResponseEntity<Object>(...);
}

Upvotes: 2

Related Questions