6

I have a ConstraintViolationException handler class that looks like this:

@Produces
@Singleton
@Requires(classes = {ConstraintViolationException.class, ExceptionHandler.class})
public class ConstraintsViolationsExceptionHandler
        implements ExceptionHandler<ConstraintViolationException, HttpResponse> {

    @Override
    public HttpResponse
    handle(HttpRequest request, ConstraintViolationException exception) {
        return HttpResponse
                .status(HttpStatus.FORBIDDEN)
                .contentType(MediaType.APPLICATION_JSON)
                .characterEncoding("UTF-8")
                .body(new SignUpPhoneNumberErrorResponse<>(400,
                        "Wrong data used",
                        new ArrayList<>(exception.getConstraintViolations())));
    }
}  

where SignUpPhoneNumberErrorResponse is my error handling POJO which is getting serialized to JSON absolutely fine.

My Controller looks like this:

@Controller(PhoneAuthAndLoginConstants.CONTROLLER_BASE_PATH)
@Validated
public class UserPhoneNumberRegistrationAndLoginController {

    @Inject
    MongoDbUserSignUpPhoneNumberDAO mongoDbUserSignUpPhoneNumberDAO;

    @Post(uri = PhoneAuthAndLoginConstants.CONTROLLER_SIGN_UP_PATH,
            consumes = MediaType.APPLICATION_JSON,
            produces = MediaType.APPLICATION_JSON)
    public Single<ResponseDataEncapsulate>
    signUpForPhoneVerification(@Valid @Body UserSignUpPhoneNumberEntity phoneNumber) {
        return mongoDbUserSignUpPhoneNumberDAO.sendVerification(phoneNumber);
    }

    @Post(uri = PhoneAuthAndLoginConstants.CONTROLLER_SIGN_UP_PATH
            +
            PhoneAuthAndLoginConstants.CONTROLLER_SIGN_UP_VERIFICATION_CODE_PARAM,
            consumes = MediaType.APPLICATION_JSON,
            produces = MediaType.APPLICATION_JSON)
    public Single<ResponseDataEncapsulate>
    sendUserSignUpConfirmation(@Valid @Body UserAccountStateSignUpEntity verificationData,
                               HttpHeaders httpHeaders) {
        return mongoDbUserSignUpPhoneNumberDAO.signUp(verificationData);
    }
}  

My POJO for UserAccountStateSignUpEntity looks like this:

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserAccountStateSignUpEntity implements UserSignUpEntity {
    @NotNull @NotBlank @Size(min = 5, max = 13) private String phoneNumber;
    @NotNull @NotBlank @Size(min = 7, max = 7) private String verificationCode;
    @JsonIgnore private Boolean verifiedAccount = Boolean.FALSE;

    public UserAccountStateSignUpEntity(String phoneNumber, String verificationCode) {
        this.phoneNumber = phoneNumber;
        this.verificationCode = verificationCode;
        this.verifiedAccount = Boolean.TRUE;
    }

    @Override
    public Map<String, Object> makePhoneEntityMapForMongo() {
        HashMap<String, Object> returnMap = new HashMap<String, Object>() {{
            put("phoneNumber", phoneNumber);
            put("verificationCode", verificationCode);
            put("verifiedAccount", verifiedAccount);
        }};

        return Collections.unmodifiableMap(returnMap);
    }
}  

I send in a request payload like this:

{
    "phoneNumber" : "91-123456789",
    "verificationCode" : "18887"
}  

This should trigger a ConstraintViolationException and my handler code should execute and I should get a HTTP Forbidden. But instead I get the default HTTP Bad Request error message.

Why isn't my handler getting executed? What can be done to make it execute?

I'm using Micronaut 1.1.3 as the web framework and the Hibernate Validator as the javax.validation implementation.

Shankha057
  • 1,296
  • 1
  • 20
  • 38
  • do you use `io.micronaut.configuration:micronaut-hibernate-validator` or original `hebernate` dependency ? take a look at https://docs.micronaut.io/1.2.0.RC1/guide/index.html#datavalidation – tsarenkotxt Jun 19 '19 at 22:16
  • @tsarenkotxt I tried using the `@Introspected` annotation to my POJOs and still no result. And only the `@Introspected` annotation made sense in my case. – Shankha057 Jun 19 '19 at 22:20
  • @tsarenkotxt I'm using Micronaut v1.1.3 and the guide that you provided has the `@Inteospected` annotation for v1.2.0.RC1 and not in v1.1.3. – Shankha057 Jun 19 '19 at 22:32
  • yes you are right about versions, also I mean dependencies in your classpath, to make sure you add `io.micronaut.configuration:micronaut-hibernate-validator` – tsarenkotxt Jun 19 '19 at 23:05
  • @tsarenkotxt I already have the required dependencies and that is visible in the logs – Shankha057 Jun 19 '19 at 23:08
  • take a look at this answer https://stackoverflow.com/a/46091234/8112217 – tsarenkotxt Jun 19 '19 at 23:19
  • @tsarenkotxt Not helpful in my case as the `message` parameter accepts a `String` and the default response is some kind of gibberish structure with the `message` field. I need to send a POJO object with a bunch of other custom fields as well. – Shankha057 Jun 19 '19 at 23:34
  • I mean you can try add `message` if it's work you send your custom exception/body with your `SignUpPhoneNumberErrorResponse`, `message` to trigger validation failure – tsarenkotxt Jun 20 '19 at 04:30
  • Hi Shankha, did you found the solution, if yes could you please share your findings, thanks – 2787184 Nov 27 '19 at 06:42

5 Answers5

2

@Error that can be applied to method to map it to an error route and SignUpPhoneNumberErrorResponse would be returned as a error response body when any ConstraintViolationException occured.

For more detail visit Micronaut docs

@Controller("/${path}")
@Validated
public class UserPhoneNumberRegistrationAndLoginController {

    @Post
    public HttpResponse method(@Valid @Body UserAccountStateSignUpEntity verificationData, HttpHeaders httpHeaders) {
        return null;
    }

    @Error(exception = ConstraintViolationException.class)
    public SignUpPhoneNumberErrorResponse onSavedFailed(HttpRequest request, ConstraintViolationException ex) {
        return new SignUpPhoneNumberErrorResponse(500,
                        "Wrong data used",
                        String.valueOf(ex.getConstraintViolations().stream().map( e -> e.getPropertyPath()+": "+e.getMessage()).collect(Collectors.toList())),
                "Application",
                "Error",
                System.currentTimeMillis());
    }

    @Error(status = HttpStatus.NOT_FOUND, global = true)  
    public HttpResponse notFound(HttpRequest request) {
        //return custom 404 error body
    }

} 
2787184
  • 3,749
  • 10
  • 47
  • 81
  • I'm aware about the `@Error` annotation but it didn't work as well. The only solution that worked for me is the one that I have answered. – Shankha057 Nov 27 '19 at 17:08
  • Same @Error didn't work on a separate listener. However, it did work for ConstraintViolationException on `@Controller` class (adding to controller class), but then it adds controller method in the failed validation path, e.g. myMethod.someClass.someFiled. It seems `@Error` must be on on `@Controller` to work. – saganas Jan 13 '21 at 12:36
2

I recently had the same problem with error handling in Micronaut. As I figured out, there is not a org.hibernate.exception.ConstraintViolationException thrown, but a javax.persistence.PersistenceException.

For me, then it worked with @Error(exception = PersistenceException::class) (Kotlin).

Roger
  • 75
  • 7
2

@Error annotation handling for ConstraintViolationException worked for me only when it is on controller.

In the end managed to handle it like this, by replacing the micronaut ConstraintExceptionHandler bean:

@Produces
@Replaces(io.micronaut.validation.exceptions.ConstraintExceptionHandler.class)
@Requires(classes = {ConstraintViolationException.class, ExceptionHandler.class})
public class ConstraintExceptionHandler extends io.micronaut.validation.exceptions.ConstraintExceptionHandler {

    @Override
    public HttpResponse handle(HttpRequest request, ConstraintViolationException exception) {
        return return HttpResponse
                .badRequest();
    }
}
saganas
  • 555
  • 4
  • 12
  • Is there is a way to hook server errors of all type and replace with own implementation facing the issue as https://stackoverflow.com/questions/65747492/global-exception-handling-in-micronaut-java – San Jaisy Jan 16 '21 at 09:11
0

I removed the @Requires(classes = {ConstraintViolationException.class, ExceptionHandler.class}) annotation and replaced the ArrayList<> in the body with a comma separated string and it started to work fine. Although I do not know the consequences of removing the annotation.

Shankha057
  • 1,296
  • 1
  • 20
  • 38
0

I believe the error here is that Micronaut already defined an exception handler for ConstraintViolationException as stated here

In order to overwrite this one I'd recommend just copying and pasting the definition from the page I linked.

@Produces
@Singleton
@Primary
@Requires(classes={javax.validation.ConstraintViolationException.class,ExceptionHandler.class})
public class ConstraintExceptionHandler
extends java.lang.Object
implements ExceptionHandler<javax.validation.ConstraintViolationException,HttpResponse<JsonError>>

(You can fill in the body. The extends java.lang.Object is not necessary)

The important part here is that I added the io.micronaut.context.annotation.Primary annotation. This will favour your handler over the other defined one. I stole this trick from ConversionErrorHandler because I was running into the same issue as you but with that handler.

Now of course you can change the HttpResponse<JsonError> to an HttpResponse<*> appropriate for your needs.

Archmede
  • 1,592
  • 2
  • 20
  • 37