Search code examples
javaresteasyjakarta-validation

Java with RestEasy: @Valid but not just on endpoint


I find myself in the following scenario, in a RestEasy REST API:

@POST
@Path("my-endpoint")
@Produces(MediaType.APPLICATION_JSON)
public Response myEndpoint(
    final @Nullable Payload payload
) {
    final var result = runCommand(
      new Command(payload),
      withPermissions(CAN_WRITE)
    );

    return ok(result);
}

...

@Value
public class Payload {
    @NotNull Long mSomeValidLong;
}

...

@Value
public class Command {
    @NotNull @Valid mPayload;
}

As you can see, the endpoint does not validate the payload immediately. Instead, there is nested logic : the payload is passed to a Command which is then executed by method runCommand. Even though you can't see how runCommand is implemented, you can see that first it checks the permissions. It also has some fancy logic for worker threads and exception catching.

Bottom line : It is the Command that must validate the parameters, (because that's when we've reached the place where the permissions have been controlled and when we're sure we have the resources for that).

Don't ask me why the permissions are not validated as an @Annotation immediately above the endpoint method, this is legacy code.

With that setup, I observe this :

This works (because RestEasy identifies the method as one of his) :

public Response myEndpoint(final @Valid @NotNull Payload payload) { ... }

This does not work because this is plain old Java :

public Command(final @Valid @NotNull Payload payload) { ... } // Constructor as it would be generated by Lombok

My question : is there any way at all to make @Valid do its magic somewhere else than in the endpoint's method parameters?

From what I read, jakarta.validation would do exactly the same kind of validation in non-RestEasy parts of the code.

Follow-up question : Is it reasonable to have two different competing validation systems? (RestEasy's validation, plus another kind such as Jakarta's validation )


Solution

  • This could be a solution : How to manually trigger Spring validation?

    1. Inject a validator

    @Autowired
    private Validator validator;
    

    2. Use it manually

    validator.validate(myObject);
    

    The validator can be the default validator :

    // JSR-303 Validator
    import javax.validation.Validator;
    

    ...or some fancy validator, for example the validator provided by Spring :

    // Spring Validator
    import org.springframework.validation.Validator;