Bean Validation для POJO

Работая с REST сервисами и используя структуры данных, которые десериализуются из JSON-a в POJO часто приходится валидировать приходящие данные. Для этого придумано множество техник, взять хотябы ну же JSON схему. Правда, такой подход не всегда является оптимальным. Иногда нам нужно, например, гибко управлять процессом валиадции, например, применять определённую логику, когда происходит исключение (Exception, невалидный документ).
В таком случае мы можем сделать одну или несколько аннотаций и валидировать приходящие данные своим процессором аннотаций.
Вот как это можно сделать:
Создаём аннотацию для проверки того, что поле в десериализованном классе - не null

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContractValueNotNull {
    String errorCode() default "";
    String logText() default "";
}

Пишем валидатор:

/**
 * Validation helper, process "@Contract-" annotations and validates contract object values accordingly
 */
public interface ContractValidator {

    /**
     * Validate field values
     *
     * @param obj particular object to run validation for
     */
    void validate(Object obj);

}

и реализацию, конечно. Обратите внимание, что здесь мы также поддерживаем коллекции List и Set, т.е. мы пройдёмся по её элементам тоже. Это удобно, например, если на самом поле (типа List или Set) аннотация не установлена, а на полях класса (из элементов списка) она присутствует. Т.е. если уж в коллекции что-то есть, то в её элементах, например, обязательно должно присутствовать поле Name.

import my.app.exceptions.IncorrectRequestException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;

@Slf4j
@Component
public class ContractValidatorImpl implements ContractValidator {

    /**
     * We shall apply recursion only to "com.mypackage..." classes
     */
    public static final String CLASS_FILTER = "mypackage";

    @Override
    public void validate(Object obj) {
        try {

            final Class<?> clazz = obj.getClass();
            final String classFullName = obj.getClass().getName();
            if (classFullName.indexOf(CLASS_FILTER) < 0) {
                return;
            }

            final Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {

                final String fieldName = field.getName();
                final Object objectOfField = FieldUtils.readField(obj, fieldName, true);
                
                   if (objectOfField != null) {
                    final Class<?> fieldClass = objectOfField.getClass();
                    if (Collection.class.isAssignableFrom(fieldClass)) {
                        final Class<?>[] interfaces = fieldClass.getInterfaces();
                        for (Class<?> ifc : interfaces) {
                            if ("java.util.List".equals(ifc.getName())) {
                                final ArrayList listElements = (ArrayList) objectOfField;
                                for (Object listElement : listElements) {
                                    validate(listElement); // Check objects in List
                                }
                                break;
                            } else if ("java.util.Set".equals(ifc.getName())) {
                                final HashSet setOfElements = (HashSet) objectOfField;
                                for (Object oneElement : setOfElements) {
                                    validate(oneElement); // Check objects in Set
                                }
                                break;
                            }
                        }

                    }
                }

                // Checking ContractValueNotNull
                if (field.isAnnotationPresent(ContractValueNotNull.class) && objectOfField == null) {
                    final ContractValueNotNull annotation = field.getAnnotation(ContractValueNotNull.class);
                    final String messageCode = annotation.errorCode();
                    String messageToLog;
                    if (!StringUtils.isBlank(annotation.logText())) {
                        messageToLog = annotation.logText();
                    } else {
                        messageToLog = "\"" + classFullName + "." + fieldName + "\" not found in request";
                    }

                    if (!StringUtils.isBlank(messageCode)) {
                        throw new IncorrectRequestException(messageCode, messageToLog);
                    } else {
                        throw new IncorrectRequestException(messageToLog);
                    }
                }

                if (objectOfField != null) {
                    validate(objectOfField); // Look for nested Objects
                }

            }
        } catch (IllegalAccessException e) {
            log.error("Annotations processing error!");
            log.error(ExceptionUtils.getStackTrace(e));
        }
    }
}

Этот валидатор рекурсивно пройдётся по полям класса, и проверит те поля, которые отмечены аннотацией, заполнены они или нет.
Помечаем поля в контролируемом классе

     @ContractValueNotNull
     public Long myfield;

     @ContractValueNotNull(errorCode="code1", logText="Wrong anotherField!")
     public MyClass anotherField;

И, соответственно, получив из REST запроса объект, валидируем его:

 @Inject // @Autowired, etc.
 private ContractValidator contractValidator;

 
public ResponseEntity<SomeType> restMethod(@RequestBody MyRequest requestContainer) {

contractValidator.validate(requestContainer);
//...
}

Всё.