What is the best way to validate constraints of a Jakarta REST sub resource?
The example is as simple as possible. It's a REST web application (Jakarta 9) containing only two resources and running in a Tomee 9.1.3 container.
package fr.laboiteadodo.trysub;
import jakarta.enterprise.context.RequestScoped;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
/**
* Root resource
*/
@RequestScoped
@Path("samples")
public class SamplesResource {
@Path("{code}")
public SampleResource getSample(@PathParam("code") String code) {
return new SampleResource(code);
}
// This method is validated
@POST
@Produces(MediaType.TEXT_PLAIN)
public String create(@FormParam("code") @NotNull String code) {
return code;
}
}
package fr.laboiteadodo.trysub;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
/**
* Sub resource
*/
public class SampleResource {
private String code;
public SampleResource(String code) {
this.code = code;
}
@GET
public String getSample() {
return code;
}
// This method is not validated
@POST
public String update(@FormParam("code") @NotNull String code) {
this.code = code;
return code;
}
}
The sub resource needs to be manualy instanciated because it cannot be managed (it declares a constructor with one parameter).
The SamplesResource.create(String)
method throws a ValidationException
if the request does not contain a parameter named code
.
The probleme is SampleResource.update(String)
method does not throw anything.
What is the best way to validate constraints of a Jakarta REST sub resource?
The example is as simple as possible. It's a REST web application (Jakarta 9) containing only two resources and running in a Tomee 9.1.3 container.
package fr.laboiteadodo.trysub;
import jakarta.enterprise.context.RequestScoped;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
/**
* Root resource
*/
@RequestScoped
@Path("samples")
public class SamplesResource {
@Path("{code}")
public SampleResource getSample(@PathParam("code") String code) {
return new SampleResource(code);
}
// This method is validated
@POST
@Produces(MediaType.TEXT_PLAIN)
public String create(@FormParam("code") @NotNull String code) {
return code;
}
}
package fr.laboiteadodo.trysub;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
/**
* Sub resource
*/
public class SampleResource {
private String code;
public SampleResource(String code) {
this.code = code;
}
@GET
public String getSample() {
return code;
}
// This method is not validated
@POST
public String update(@FormParam("code") @NotNull String code) {
this.code = code;
return code;
}
}
The sub resource needs to be manualy instanciated because it cannot be managed (it declares a constructor with one parameter).
The SamplesResource.create(String)
method throws a ValidationException
if the request does not contain a parameter named code
.
The probleme is SampleResource.update(String)
method does not throw anything.
1 Answer
Reset to default 0The problem is as you stated:
The sub resource needs to be manualy instanciated because it cannot be managed (it declares a constructor with one parameter).
It is not managed, therefore @FormParam("code") ...
on its method does not take effect. I can think of three ways to circumvent this limitation - but only circumvent:
Reconsider if you actually need a subresource. Is it so crucial for you/your design to pass the request-specific
code
to the constructor of the resource? In the example it is not necessary, i.e. the subresource could be written as:@Path("samples/{code}") @POST public String update( @PathParam("code") codeFromPath, @FormParam("code") @NotNull String code) ...
Do manual validation using the Bean Validation framework. This doesn't make sense for a simple string (the
@FormParam("code") @NotNull String code
), but it does make sens if you have a more complex object. You can inject thejakarta.validation.Validator
to the parent resource, pass it to the subresource and use it from there:validator.validate(moreComplexObject);
You probably still need to do the not-null validation by hand.
Keep the web layer thin and delegate all business logic to a service layer (validation is business logic). This is similar to (2) in that you will need to inject the service in the parent resource (
SamplesResource
) and pass it manually to the child resource. But you don't have to deal with low-level stuff like the validator; you just make sure that the service method contains the right validation annotations:@ApplicationScoped public class SampleServiceImpl implements SampleService { public String update(@NotNull String code) ...
...and just pass the raw
@FormParam("code") code
:@POST public String update(@FormParam("code") String code) { return service.update(code); // -OR- (to be closer to the question: this.code = service.update(code); return code; }
I feel that (3) is the cleaner and more generic approach.