Repository: quarkusio/quarkus
Issue: Bean Validation on Vert.x Routes is not automatically mapped to JSON

**Describe the bug**
When using constraints on param binding:

```
@Route(produces = "application/json")
Person createPerson(@NonNull @Param("id") String primaryKey) {
  // ...
}
```

And calling this route directly from a browser or curl with a wrong value, the output is not mapped to JSON. 

This is working fine when validating responses:

```
@Route
@Valid Uni<Person> createPerson(...) {
  // ...
}
```

If the returned person is not validated, now the output is properly mapped to JSON as expected. Moreover, I didn't need to set the `produces = "application/json"` in the `@Route` annotation. 

Note that as a workraround, the only way to make this work is to send the HTTP header "ACCEPT: application/json". 

When using resteasy jackson extension:

```
@Path("/rest-query")
public class QueryResource {
    @GET
    public Greeting getGreetingWithName(@Pattern(regexp = "ne.*") @NotNull @QueryParam("name") String name) {
        return new Greeting(name, "hi");
    }
}
```

This is also working as expected mapping the validation errors into JSON.

**Expected behavior**
It should map the constraint errors into JSON as done when validating responses and also using other Quarkus extensions like Resteasy.

**Actual behavior**
The constraint errors are always in HTML format unless you set the HTTP header "ACCEPT: application/json".

**Environment (please complete the following information):**
 - Output of `java -version`: 11
 - Quarkus version or git rev: 1.12.0.Final

Changed files:
extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/Methods.java
extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java
extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/validation/SyncValidationTest.java
extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/validation/MultiValidationTest.java
extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/ValidationSupport.java

diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/Methods.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/Methods.java
index d75069f9569..02f5bb89f32 100644
--- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/Methods.java
+++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/Methods.java
@@ -180,7 +180,7 @@ class Methods {
     static final MethodDescriptor VALIDATION_HANDLE_VIOLATION_EXCEPTION = MethodDescriptor
             .ofMethod(ValidationSupport.class.getName(), "handleViolationException",
                     Void.TYPE.getName(), Methods.VALIDATION_CONSTRAINT_VIOLATION_EXCEPTION,
-                    RoutingContext.class.getName());
+                    RoutingContext.class.getName(), Boolean.TYPE.getName());
 
     static final MethodDescriptor VALIDATOR_VALIDATE = MethodDescriptor
             .ofMethod("javax.validation.Validator", "validate", "java.util.Set",
diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java
index 5b657ef8a90..d5a94b9e2d9 100644
--- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java
+++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java
@@ -744,9 +744,11 @@ void implementInvoke(HandlerDescriptor descriptor, BeanInfo bean, MethodInfo met
                 block.assign(res, value);
             }
             CatchBlockCreator caught = block.addCatch(Methods.VALIDATION_CONSTRAINT_VIOLATION_EXCEPTION);
+            boolean forceJsonEncoding = !descriptor.isContentTypeString() && !descriptor.isContentTypeBuffer()
+                    && !descriptor.isContentTypeMutinyBuffer();
             caught.invokeStaticMethod(
                     Methods.VALIDATION_HANDLE_VIOLATION_EXCEPTION,
-                    caught.getCaughtException(), invoke.getMethodParam(0));
+                    caught.getCaughtException(), invoke.getMethodParam(0), invoke.load(forceJsonEncoding));
             caught.returnValue(caught.loadNull());
         }
 
@@ -1028,7 +1030,7 @@ private ResultHandle getContentToWrite(HandlerDescriptor descriptor, ResultHandl
         // Encode to Json
         Methods.setContentTypeToJson(response, writer);
         // Validate res if needed
-        if (descriptor.isProducedResponseValidated()) {
+        if (descriptor.isProducedResponseValidated() && (descriptor.isReturningUni() || descriptor.isReturningMulti())) {
             return Methods.validateProducedItem(response, writer, res, validatorField, owner);
         } else {
             return writer.invokeStaticMethod(Methods.JSON_ENCODE, res);
diff --git a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/validation/MultiValidationTest.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/validation/MultiValidationTest.java
index 07324d60f9d..99c88b953ff 100644
--- a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/validation/MultiValidationTest.java
+++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/validation/MultiValidationTest.java
@@ -49,15 +49,6 @@ public void test() {
                 .body("details", containsString("validation constraint violations"))
                 .body("violations[0].field", containsString("name"))
                 .body("violations[0].message", is(not(emptyString())));
-
-        // Input parameter violation - HTML
-        given()
-                .queryParam("name", "doesNotMatch")
-                .when()
-                .get("/query")
-                .then().statusCode(400)
-                .body(containsString("ConstraintViolation"))
-                .body(is(not(emptyString())));
     }
 
     @ApplicationScoped
diff --git a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/validation/SyncValidationTest.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/validation/SyncValidationTest.java
index ff11ff84bd5..9dd5d07b0bb 100644
--- a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/validation/SyncValidationTest.java
+++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/validation/SyncValidationTest.java
@@ -45,6 +45,25 @@ public void test() {
                 .get("/query")
                 .then().statusCode(200);
 
+        // Invalid parameter
+        given()
+                .when()
+                .get("/invalid-param")
+                .then()
+                .statusCode(400)
+                .body("title", containsString("Constraint Violation"))
+                .body("status", is(400))
+                .body("details", containsString("validation constraint violations"))
+                .body("violations[0].field", containsString("name"))
+                .body("violations[0].message", is(not(emptyString())));
+
+        // Invalid parameter - HTML output
+        get("/invalid-param-html")
+                .then()
+                // the return value is ok but the param is invalid
+                .statusCode(400)
+                .body(containsString("ConstraintViolation"), is(not(emptyString())));
+
         // JSON output
         given()
                 .header("Accept", "application/json")
@@ -58,12 +77,6 @@ public void test() {
                 .body("violations[0].field", containsString("name"))
                 .body("violations[0].message", is(not(emptyString())));
 
-        // HTML output
-        get("/invalid")
-                .then()
-                .statusCode(500)
-                .body(containsString("ConstraintViolation"), is(not(emptyString())));
-
         given()
                 .header("Accept", "application/json")
                 .when()
@@ -89,19 +102,21 @@ public void test() {
                 .body("violations[0].field", containsString("name"))
                 .body("violations[0].message", is(not(emptyString())));
 
-        // Input parameter violation - HTML
+        // Input parameter violation - JSON
         given()
                 .queryParam("name", "doesNotMatch")
                 .when()
                 .get("/query")
                 .then().statusCode(400)
-                .body(containsString("ConstraintViolation"))
-                .body(is(not(emptyString())));
+                .body("title", containsString("Constraint Violation"))
+                .body("status", is(400))
+                .body("details", containsString("validation constraint violations"));
     }
 
     @ApplicationScoped
     public static class MyRoutes {
 
+        @Valid
         @Route(methods = HttpMethod.GET, path = "/valid")
         public Greeting getValidGreeting() {
             return new Greeting("luke", "hello");
@@ -118,11 +133,21 @@ public Greeting getInvalidValidGreeting() {
             return new Greeting("neo", "hi");
         }
 
+        @Route
+        public Greeting invalidParam(@NotNull @Param String name) {
+            return new Greeting("neo", "hi");
+        }
+
         @Route(methods = HttpMethod.GET, path = "/query")
         public Greeting getGreetingWithName(@Pattern(regexp = "ne.*") @NotNull @Param("name") String name) {
             return new Greeting(name, "hi");
         }
 
+        @Route
+        public String invalidParamHtml(@NotNull @Param String name) {
+            return "hi";
+        }
+
     }
 
     public static class Greeting {
diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/ValidationSupport.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/ValidationSupport.java
index 259568625c4..24217fe968a 100644
--- a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/ValidationSupport.java
+++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/ValidationSupport.java
@@ -64,9 +64,9 @@ private static JsonObject generateJsonResponse(Set<ConstraintViolation<?>> viola
         return json;
     }
 
-    public static void handleViolationException(ConstraintViolationException ex, RoutingContext rc) {
+    public static void handleViolationException(ConstraintViolationException ex, RoutingContext rc, boolean forceJsonEncoding) {
         String accept = rc.request().getHeader(ACCEPT_HEADER);
-        if (accept != null && accept.contains(APPLICATION_JSON)) {
+        if (forceJsonEncoding || accept != null && accept.contains(APPLICATION_JSON)) {
             rc.response().putHeader(RouteHandlers.CONTENT_TYPE, APPLICATION_JSON);
             JsonObject json = generateJsonResponse(ex.getConstraintViolations(), false);
             rc.response().setStatusCode(json.getInteger(PROBLEM_STATUS));