How can I programmatically call the same validator that runs on a @RequestMethod method with a @Valid parameter in Spring?

Daniel picture Daniel · Jul 4, 2013 · Viewed 11.7k times · Source

I have a class with hibernate's validation annotation on some fields (such as @NotNull and @Size(min = 4, max = 50), etc...)

public class MyClass {

    Long id;

    @NotEmpty
    @Size(min = 4, max = 50)
    String machineName;

    @NotEmpty
    @Size(min = 4, max = 50)
    String humanName;

    // Getters, setters, etc…
}

I also have a custom controller that acts as a JSON API, and a JSON deserializer that creates MyClass objects when API methods are called. In my custom controller I have a method to create a new object of that type:

@RequestMapping(method = RequestMethod.POST)
public long createMyObject(@RequestBody @Valid MyClass newObj) {
    // Create the object in the database
    return newObj.getId();
}

and another method that updates an existing object

@RequestMapping(method = RequestMethod.PUT)
public void updateMyObject(@RequestBody MyClass updatedObj) {
    MyClass existingObj = // Get existing obj from DB by updatedObj.getId();

    // Do some secondary validation, such as making sure that a specific
    // field remains unchanged compared to the existing instance
    if (existingObj.getMachineName() != null && 
            !existingObj.getMachineName().equals(updatedObj.getMachineName())) {
        throw new CannotChangeMachineNameException();
    }
    else {
        updatedObj.setMachineName(existingObj.getMachineName());
    }

    // [HERE IS WHERE I WANT THE MAGIC TO HAPPEN]

    // Save updatedObj to the database
}

While I can use @Valid in createMyObject, I cannot use it in updateMyObject because our API implementation requires that machineName remains unchanged - users can call the API with a JSON object that either excludes machineName entirely or populate it with the same value that exists in the database.*

Before saving the updated object to the database I want to call the same validator that having the @Valid annotation would cause to be called. How can I find this validator and use it?

Answer

CorayThan picture CorayThan · Jul 4, 2013

Nothing says you need to use @Valid in your controller methods only. Why not make a validation method that accepts a parameter you annotate as @Valid, then just return that same parameter.

Like this:

public Book validateBook(@Valid Book book) {
   return book;
}

Looks like an alternative would be to use Hibernate's validation package. Here's it's documentation.

Basically, you get a Validator from a ValidationFactory, and then use the validator like this:

 @Test
    public void manufacturerIsNull() {
        Car car = new Car(null, "DD-AB-123", 4);

        Set<ConstraintViolation<Car>> constraintViolations =
            validator.validate(car);

        assertEquals(1, constraintViolations.size());
        assertEquals("may not be null", constraintViolations.iterator().next().getMessage());
}