diff --git a/pom.xml b/pom.xml
index 1fffb73..79a3d2b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -160,6 +160,26 @@
jackson-databind
${jackson.version}
+
+ org.hibernate.validator
+ hibernate-validator
+ 6.0.7.Final
+
+
+ javax.el
+ javax.el-api
+ 3.0.0
+
+
+ org.glassfish.web
+ javax.el
+ 2.2.4
+
+
+ org.json
+ json
+ 20170516
+
jersey-test-framework-provider-inmemory
org.glassfish.jersey.test-framework.providers
@@ -231,6 +251,12 @@
common.rest.schemagen
0.18.9
+
+ org.assertj
+ assertj-core
+ 3.8.0
+ test
+
diff --git a/src/main/java/com/mercateo/rest/jersey/utils/exception/ConstrainViolationExceptionMapper.java b/src/main/java/com/mercateo/rest/jersey/utils/exception/ConstrainViolationExceptionMapper.java
new file mode 100644
index 0000000..86f1504
--- /dev/null
+++ b/src/main/java/com/mercateo/rest/jersey/utils/exception/ConstrainViolationExceptionMapper.java
@@ -0,0 +1,48 @@
+package com.mercateo.rest.jersey.utils.exception;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
+
+@Slf4j
+@Provider
+public class ConstrainViolationExceptionMapper implements ExceptionMapper{
+
+ @Override
+ public Response toResponse(ConstraintViolationException violationException) {
+ List errors = toValidationErrors(violationException);
+
+ log.error("Sending error response to client {}", errors
+ .stream()
+ .map(ValidationError::toString)
+ .collect(Collectors.joining(",")));
+
+ return Response
+ .status(BAD_REQUEST)
+ .entity(new ValidationExceptionJson("Invalid",
+ BAD_REQUEST.getStatusCode(),
+ "The request body is syntactically correct, but is not accepted, because of its data.",
+ errors))
+ .type("application/problem+json")
+ .build();
+ }
+
+ private List toValidationErrors(ConstraintViolationException violationException){
+ return violationException
+ .getConstraintViolations()
+ .stream() //
+ .map(ValidationError::of)
+ .collect(Collectors.toList());
+
+ }
+
+}
diff --git a/src/main/java/com/mercateo/rest/jersey/utils/exception/SimpleExceptionJson.java b/src/main/java/com/mercateo/rest/jersey/utils/exception/SimpleExceptionJson.java
index d8cc3de..8bba6bb 100644
--- a/src/main/java/com/mercateo/rest/jersey/utils/exception/SimpleExceptionJson.java
+++ b/src/main/java/com/mercateo/rest/jersey/utils/exception/SimpleExceptionJson.java
@@ -1,9 +1,13 @@
package com.mercateo.rest.jersey.utils.exception;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
import lombok.NonNull;
-import lombok.Value;
-@Value
+@Getter
+@AllArgsConstructor
+@EqualsAndHashCode
public class SimpleExceptionJson {
@NonNull
String title;
diff --git a/src/main/java/com/mercateo/rest/jersey/utils/exception/ValidationError.java b/src/main/java/com/mercateo/rest/jersey/utils/exception/ValidationError.java
new file mode 100644
index 0000000..ac07b90
--- /dev/null
+++ b/src/main/java/com/mercateo/rest/jersey/utils/exception/ValidationError.java
@@ -0,0 +1,78 @@
+package com.mercateo.rest.jersey.utils.exception;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.mercateo.rest.jersey.utils.validation.EnumValue;
+import com.mercateo.rest.jersey.utils.validation.NullOrNotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ElementKind;
+import javax.validation.Path;
+import javax.validation.constraints.*;
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+
+@Getter
+@AllArgsConstructor
+@RequiredArgsConstructor
+public class ValidationError {
+
+ @NonNull
+ ValidationErrorCode code;
+
+ @NonNull
+ String path;
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ Integer limit;
+
+ public static ValidationError of(ConstraintViolation> constraintViolation){
+ Annotation annotation = constraintViolation.getConstraintDescriptor().getAnnotation();
+ String path = constructJsonPath(constraintViolation.getPropertyPath());
+
+ ValidationError error = new ValidationError(ValidationErrorCode.UNKNOWN, path);
+
+ if(annotation instanceof NotNull || annotation instanceof NotBlank || annotation instanceof AssertTrue){
+ return new ValidationError(ValidationErrorCode.REQUIRED, path);
+ } else if(annotation instanceof Size){
+ final Size sizeAnnotation = (Size) annotation;
+ final Object value = constraintViolation.getInvalidValue();
+
+ if (value instanceof String && value.toString().length() < sizeAnnotation.min()) {
+ error = new ValidationError(ValidationErrorCode.MINLENGTH, path, sizeAnnotation.min());
+ } else if (value instanceof String && value.toString().length() > sizeAnnotation.max()) {
+ error = new ValidationError(ValidationErrorCode.MAXLENGTH, path, sizeAnnotation.max());
+ } else if (value instanceof Collection && ((Collection>) value).size() < sizeAnnotation.min()) {
+ error = new ValidationError(ValidationErrorCode.MINITEMS, path, sizeAnnotation.min());
+ } else if (value instanceof Collection && ((Collection>) value).size() > sizeAnnotation.max()) {
+ error = new ValidationError(ValidationErrorCode.MAXITEMS, path, sizeAnnotation.max());
+ }
+ } else if(annotation instanceof EnumValue){
+ error = new ValidationError(ValidationErrorCode.ENUM, path);
+ } else if(annotation instanceof NullOrNotBlank){
+ error = new ValidationError(ValidationErrorCode.INVALID, path);
+ } else if (annotation instanceof Min) {
+ final Min min = (Min) annotation;
+ error = new ValidationError(ValidationErrorCode.MINLENGTH, path, ((int) min.value()));
+ } else if (annotation instanceof Max) {
+ Max max = (Max) annotation;
+ error = new ValidationError(ValidationErrorCode.MAXLENGTH, path, ((int) max.value()));
+ } else if (annotation instanceof Email) {
+ error = new ValidationError(ValidationErrorCode.INVALID_EMAIL, path);
+ }
+ return error;
+ }
+
+ private static String constructJsonPath(Path path) {
+ StringBuilder jsonPath = new StringBuilder("#");
+ path.forEach(pathComponent -> {
+ if (pathComponent.getKind() == ElementKind.PROPERTY) {
+ jsonPath.append("/").append(pathComponent.getName());
+ }
+ });
+ return jsonPath.toString();
+ }
+}
diff --git a/src/main/java/com/mercateo/rest/jersey/utils/exception/ValidationErrorCode.java b/src/main/java/com/mercateo/rest/jersey/utils/exception/ValidationErrorCode.java
new file mode 100644
index 0000000..3a28248
--- /dev/null
+++ b/src/main/java/com/mercateo/rest/jersey/utils/exception/ValidationErrorCode.java
@@ -0,0 +1,19 @@
+package com.mercateo.rest.jersey.utils.exception;
+
+public enum ValidationErrorCode {
+
+ REQUIRED,
+ PATTERN,
+ TYPE,
+ MINLENGTH,
+ MAXLENGTH,
+ MINIMUM,
+ MAXIMUM,
+ INVALID,
+ MINITEMS,
+ MAXITEMS,
+ ENUM,
+ INVALID_EMAIL,
+ UNKNOWN
+
+}
diff --git a/src/main/java/com/mercateo/rest/jersey/utils/exception/ValidationExceptionJson.java b/src/main/java/com/mercateo/rest/jersey/utils/exception/ValidationExceptionJson.java
new file mode 100644
index 0000000..4a91e97
--- /dev/null
+++ b/src/main/java/com/mercateo/rest/jersey/utils/exception/ValidationExceptionJson.java
@@ -0,0 +1,16 @@
+package com.mercateo.rest.jersey.utils.exception;
+
+import lombok.Getter;
+
+import java.util.List;
+
+@Getter
+public class ValidationExceptionJson extends SimpleExceptionJson {
+
+ List errors;
+
+ public ValidationExceptionJson(String title, int status, String detail,List errors) {
+ super(title, status, detail);
+ this.errors = errors;
+ }
+}
diff --git a/src/main/java/com/mercateo/rest/jersey/utils/validation/EnumValue.java b/src/main/java/com/mercateo/rest/jersey/utils/validation/EnumValue.java
new file mode 100644
index 0000000..91362c7
--- /dev/null
+++ b/src/main/java/com/mercateo/rest/jersey/utils/validation/EnumValue.java
@@ -0,0 +1,23 @@
+package com.mercateo.rest.jersey.utils.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+@Documented
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = EnumValueValidator.class)
+public @interface EnumValue {
+
+ Class> targetEnum();
+
+ Class>[] groups() default {};
+
+ String message() default "INVALID_VALUE";
+
+ Class extends Payload>[] payload() default {};
+
+
+}
+
diff --git a/src/main/java/com/mercateo/rest/jersey/utils/validation/EnumValueValidator.java b/src/main/java/com/mercateo/rest/jersey/utils/validation/EnumValueValidator.java
new file mode 100644
index 0000000..b1da46d
--- /dev/null
+++ b/src/main/java/com/mercateo/rest/jersey/utils/validation/EnumValueValidator.java
@@ -0,0 +1,26 @@
+package com.mercateo.rest.jersey.utils.validation;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class EnumValueValidator implements ConstraintValidator {
+
+ private Class targetEnum;
+
+ @Override
+ public void initialize(EnumValue enumValue) {
+ this.targetEnum = enumValue.targetEnum();
+ }
+
+ @Override
+ public boolean isValid(CharSequence value, ConstraintValidatorContext constraintValidatorContext) {
+ List