Effortless Method Validation: Combining JSR303 and AspectJ Magic

This technical guide demonstrates how to implement automated method validation in Java using JSR303 (Bean Validation) and AspectJ. By combining Hibernate Validator's method validation capabilities with AspectJ's aspect-oriented programming features, developers can enforce method pre- and post-conditions declaratively. The solution uses annotations like @NotNull and implements validation through aspects, throwing ConstraintViolationException when validation fails. This approach keeps the validation logic separate from business code, resulting in cleaner and more maintainable applications.

2 Minutes reading time

Behold the masterpiece that AI hallucinated while reading this post:

"How Little @NotNull Made Java Methods Happy and Safe"

(after I fed it way too many marketing blogs and memes)

Created using DALL-E 3

AI-Generated: How Little @NotNull Made Java Methods Happy and Safe

Hibernate Validator since version 4.2 supports method validation. For instance consider the following Java code:

package de.mirkosertic.aspectj;

import javax.validation.constraints.NotNull;

public class Example {

    public void doNothing(String aValue) {
    }

    public String getString(String aValue) {
        return aValue;
    }

    public @NotNull String getNullString(String aValue) {
        return aValue;
    }

    public void doNothingSingleNotNull(@NotNull String aValue) {
    }

    public void doNothingMiddleNotNull(String aStart,@NotNull String aValue, String aEnd) {
    }

    public void doNothingEndNotNull(String aStart,String aValue, @NotNull String aEnd) {
    }
}

Wouldn’t it be cool to validate the method pre- and post conditions without coding too much? Here comes method validation and AspectJ to play. Using the following Aspect, we can validate the conditions using JSR303. If an argument does not match or validation fails, a ConstraintViolationException is thrown.

package de.mirkosertic.aspectj;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;

import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableValidator;
import java.util.Set;

@Aspect
public class ValidatorAspect {

    private ValidatorFactory validatorFactory;

    public ValidatorAspect() {
        validatorFactory = Validation.buildDefaultValidatorFactory();
    }

    @Around("execution(@javax.validation.constraints.* public * * (..))")
    public Object afterReturning(ProceedingJoinPoint aJoinPoint) throws Throwable {

        Object theReturnValue = aJoinPoint.proceed();

        MethodSignature theSignature = (MethodSignature) aJoinPoint.getSignature();
        ExecutableValidator theValidator = validatorFactory.getValidator().forExecutables();
        Set<ConstraintViolation<Object>> theViolations = theValidator.validateReturnValue(aJoinPoint.getTarget(),
          theSignature.getMethod(), theReturnValue);

        if (theViolations.size()> 0) {
            throw new ConstraintViolationException(theViolations);
        }

        return theReturnValue;
    }

    @Around("execution(public * * (.., @javax.validation.constraints..* (*), ..))")
    public Object pointcutMethodArgument(ProceedingJoinPoint aJoinPoint) throws Throwable {
        return validateInvocation(aJoinPoint);
    }

    private Object validateInvocation(ProceedingJoinPoint aJoinPoint) throws Throwable {

        MethodSignature theSignature = (MethodSignature) aJoinPoint.getSignature();

        ExecutableValidator theValidator = validatorFactory.getValidator().forExecutables();
        Set<ConstraintViolation<Object>> theViolations = theValidator.validateParameters(aJoinPoint.getTarget(),
         theSignature.getMethod(), aJoinPoint.getArgs());

        if (theViolations.size()> 0) {
            throw new ConstraintViolationException(theViolations);
        }

        return aJoinPoint.proceed();
    }
}

Nice! For further reading, please check the Hibernate Validator documentation.

Git revision: 2e692ad