In Spring MVC. I'm able to use the WebRequest as a parameter that will automatically be set (along with other things such as Locale etc.).
I'm also using #RequestBody to pass in a JSON object which describes what and how data should be fetched.
Is it possible to get Spring to automatically set the WebRequest directly on the #RequestBody object EntriesRequestDTO (I would make a WebRequest field on EntriesRequestDTO). This would allow me to hide some complexity since I often need to get an attribute from WebRequest.
#RequestMapping(value = "/entries", method = { RequestMethod.POST })
public EntriesDTO getEntries(#RequestBody EntriesRequestDTO request, WebRequest webRequest){
...
}
You can use AOP to set the WebRequest to the DTO
#Around("execution(* (com.your.company..*).*(..))")
public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
final Object[] args = joinPoint.getArgs();
//get args[0] and args[1]
return joinPoint.proceed();
}
Make sure the regex is correct (or add custom annotation), check for cast exceptions.
I ended up making a custom HttpMessageConverter. It is not a very general solution, but it works for me:
#Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {
#Bean
public DTOJackonMessageConverter customJackson2HttpMessageConverter() {
return new DTOJackonMessageConverter();
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(customJackson2HttpMessageConverter());
super.addDefaultHttpMessageConverters(converters);
}
}
The DTOJacksonMessageConverter extends MappingJackson2HttpMessageConverter and overrides read method.
Related
Given the following basic domain model:
abstract class BaseData { ... }
class DataA extends BaseData { ... }
class DataB extends BaseData { ... }
I want to write a Spring MVC controller endpoint thus ...
#PostMapping(path="/{typeOfData}", ...)
ResponseEntity<Void> postData(#RequestBody BaseData baseData) { ... }
The required concrete type of baseData can be inferred from the typeOfData in the path.
This allows me to have a single method that can handle multiple URLs with different body payloads. I would have a concrete type for each payload but I don't want to have to create multiple controller methods that all do the same thing (albeit each would do very little).
The challenge that I am facing is how to "inform" the deserialization process so that the correct concrete type is instantiated.
I can think of two ways to do this.
First use a custom HttpMessageConverter ...
#Bean
HttpMessageConverter httpMessageConverter() {
return new MappingJackson2HttpMessageConverter() {
#Override
public Object read(final Type type, final Class<?> contextClass, final HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// TODO How can I set this dynamically ?
final Type subType = DataA.class;
return super.read(subType, contextClass, inputMessage);
}
};
}
... which gives me the challenge to determine the subType based on the HttpInputMessage. Possibly I could use a Filter to set a custom header earlier when the URL is available to me, or I could use a ThreadLocal also set via a Filter. Neither sounds ideal to me.
My second approach would be to again use a Filter and this time wrap the incoming payload in an outer object which would then provide the type in a way that enables Jackson to do the work via #JsonTypeInfo. At the moment this is probably my preferred approach.
I have investigated HandlerMethodArgumentResolver but if I try to register a custom one it is registered AFTER the RequestResponseBodyMethodProcessor and that class takes priority.
Hmm, so after typing all of that out I had a quick check of something in the RequestResponseBodyMethodProcessor before posting the question and found another avenue to explore, which worked neatly.
Excuse the #Configuration / #RestController / WebMvcConfigurer mash-up and public fields, all for brevity. Here's what worked for me and achieved exactly what I wanted:
#Configuration
#RestController
#RequestMapping("/dummy")
public class DummyController implements WebMvcConfigurer {
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#interface BaseData {}
public static class AbstractBaseData {}
public static class DataA extends AbstractBaseData {
public String a;
}
public static class DataB extends AbstractBaseData {
public String b;
}
private final MappingJackson2HttpMessageConverter converter;
DummyController(final MappingJackson2HttpMessageConverter converter) {
this.converter = converter;
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(
new RequestResponseBodyMethodProcessor(Collections.singletonList(converter)) {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(BaseData.class)
&& parameter.getParameterType() == AbstractBaseData.class;
}
#Override
protected <T> Object readWithMessageConverters(
NativeWebRequest webRequest, MethodParameter parameter, Type paramType)
throws IOException, HttpMediaTypeNotSupportedException,
HttpMessageNotReadableException {
final String uri =
webRequest.getNativeRequest(HttpServletRequest.class).getRequestURI();
return super.readWithMessageConverters(
webRequest, parameter, determineActualType(webRequest, uri));
}
private Type determineActualType(NativeWebRequest webRequest, String uri) {
if (uri.endsWith("data-a")) {
return DataA.class;
} else if (uri.endsWith("data-b")) {
return DataB.class;
}
throw new HttpMessageNotReadableException(
"Unable to determine actual type for request URI",
new ServletServerHttpRequest(
webRequest.getNativeRequest(HttpServletRequest.class)));
}
});
}
#PostMapping(
path = "/{type}",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<? extends AbstractBaseData> post(#BaseData AbstractBaseData baseData) {
return ResponseEntity.ok(baseData);
}
}
The key to this is that I stopped using #RequestBody because that is what was preventing me overriding the built-in behaviour. By using #BaseData instead I get a HandlerMethodArgumentResolver that uniquely supports the parameter.
Other than that it was a case of assembling the two objects that already did what I needed, so autowire a MappingJackson2HttpMessageConverter and instantiate a RequestResponseBodyMethodProcessor with that one converter. Then pick the right method to override so that I could control what parameter type was used at a point that I had access to the URI.
Quick test. Given the following payload for both requests ...
{
"a": "A",
"b": "B"
}
POST http://localhost:8081/dummy/data-a
... gives a response of ...
{
"a": "A"
}
POST http://localhost:8081/dummy/data-b
... gives a response of ...
{
"b": "B"
}
In our real-world example this means that we will be able to write one method each that supports the POST / PUT. We need to build the objects and configure the validation possibly - or alternatively if we use OpenAPI 3.0 which we are investigating we could generate the model and validate without writing any further code ... but that's a separate task ;)
I am trying to use Spring validation with a controller interface generated by swagger-codegen. The swagger code generation supplies an abstract class for a controller. Our controller implements the codegen class and provides the actual logic. I would like to access the BindingResult in my controller methods, but swagger-codegen does not generate that parameter in its interface. Is there any way to get ahold of the BindingResults object other than specifying it as a parameter?
To make this more concrete, the codegen makes the endpoint like this (noisy code removed):
#RequestMapping(value = "/api/repository/v1/datasets",
produces = { "application/json" },
consumes = { "application/json" },
method = RequestMethod.POST)
default ResponseEntity<JobModel> createDataset(#Valid #RequestBody DatasetRequestModel dataset) {
...
}
We implement a controller with the usual binder setup like:
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.addValidators(requestValidator)
}
but within the endpoint, we have no way to get the BindingResult since it has to match the signature of the codegen entry:
public ResponseEntity<StudySummaryModel> createStudy(#Valid #RequestBody StudyRequestModel studyRequest) {
...
}
I think the most straightforward solution may be to skip using WebDataBinder. Instead, I can have each controller endpoint call validators directly.
I found another approach besides hand coding the validation; using an #ControllerAdvice class that extends ResponseEntityExceptionHandler.
There is a nice example here: Spring Validation Example
Here is my code based on that example that formats the error:
#ControllerAdvice
public class ApiValidationExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request
) {
BindingResult bindingResult = ex.getBindingResult();
List<String> errorDetails = bindingResult
.getFieldErrors()
.stream()
.map(err -> err.getCode() + " error on '" + err.getObjectName() + "': " + err.getDefaultMessage())
.collect(toList());
ErrorModel errorModel = new ErrorModel()
.message("Validation errors - see error details")
.errorDetail(errorDetails);
return new ResponseEntity<>(errorModel, HttpStatus.BAD_REQUEST);
}
}
I am having a Spring controller with a Validator defined as:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new MyValidator(myService));
}
And calling it:
public ResponseEntity<?> executeSomething(
#ApiParam(name = "monitorRequest", required = true, value = "") #Valid #RequestBody MonitorRequest monitorRequest,
HttpServletRequest request, HttpServletResponse response) throws RESTException
I need to add one more Validator for this controller that could be called from some specific methods of this controller. Is there any way to achieve this?
EDIT: I am handling the Error by:
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseBody
public ResponseEntity<?> processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
ValidationErrorObj obj = processFieldErrors(fieldErrors);
ResponseEntity r = new ResponseEntity(obj, HttpStatus.BAD_REQUEST);
return r;
}
You can have more than one InitBinder method in a controller. It is controlled by the optional value parameter . For the javadoc of InitBinder : String[] value : The names of command/form attributes and/or request parameters that this init-binder method is supposed to apply to ... Specifying model attribute names or request parameter names here restricts the init-binder method to those specific attributes/parameters, with different init-binder methods typically applying to different groups of attributes or parameters.
Another way would be to explicely call a complementary Validator in specific methods.
BTW : I can't see any Errors or BindingResult in your controller method signature : where do you find whether errors occured ?
For those who are still trying to figure out how to solve this in 2017. I was facing similar issues while trying to implement 2 validators in my RestController. I followed the approach mentioned above by #Serge Ballasta.
I ended up making 2 Model each of linked to their specific Validators. The Controller methods look something like
#RequestMapping(value = "register", method = RequestMethod.POST)
public ResponseEntity<User> register(#Valid #RequestBody UserRegisterRequest userRegisterRequest) {
return null;
}
#RequestMapping(value = "test", method = RequestMethod.POST)
public ResponseEntity<?> test(#Valid #RequestBody TestRequest testRequest) {
return null;
}
and I created 2 initBinders to wire these validators in the controller like
#InitBinder("testRequest")
public void setupBinder(WebDataBinder binder) {
binder.addValidators(testValidator);
}
#InitBinder("userRegisterRequest")
public void setupBinder1(WebDataBinder binder) {
binder.addValidators(userRegistrationRequestValidator);
}
Please note that the #RequestBody attributes (userRegisterRequest , testRequest) had to be provided as values in the #InitBinder() annotations.
By the way the in my code I handle the bindingResult in a custom ExceptionHandler class which extends ResponseEntityExceptionHandler which gives me freedom to do custom handling of the response.
I have created a Spring MVC REST service using Bean Validation 1.2 with the following method:
#RequestMapping(value = "/valid")
public String validatedMethod(#Valid ValidObject object) {
}
If object isn't valid, Tomcat informs me that The request sent by the client was syntactically incorrect. and my validatedMethod is never called.
How can I get the message that was defined in the ValidObject bean? Should I use some filter or interceptor?
I know that I can rewrite like below, to get the set of ConstraintViolations from the injected Validator, but the above seems more neat...
#RequestMapping(value = "/valid")
public String validatedMethod(ValidObject object) {
Set<ConstraintViolation<ValidObject>> constraintViolations = validator
.validate(object);
if (constraintViolations.isEmpty()) {
return "valid";
} else {
final StringBuilder message = new StringBuilder();
constraintViolations.forEach((action) -> {
message.append(action.getPropertyPath());
message.append(": ");
message.append(action.getMessage());
});
return message.toString();
}
}
I believe a better way of doing this is using ExceptionHandler.
In your Controller you can write ExceptionHandler to handle different exceptions. Below is the code for the same:
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationFailureResponse validationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
final List<FieldError> fieldErrors = result.getFieldErrors();
return new ValidationFailureResponse((FieldError[])(fieldErrors.toArray(new FieldError[fieldErrors.size()])));
}
When you send a bad request to the Controller, the validator throws an exception of type MethodArgumentNotValidException. So the ideal way would be to write an exception handler to specifically handle this exception.
There you can create a beautiful response to tell the user of things which went wrong.
I advocate this, because you have to write this just once and many Controller methods can use it. :)
UPDATE
When you use the #Valid annotation for a method argument in the Controller, the validator is invoked automatically and it tries to validate the object, if the object is invalid, it throws MethodArgumentNotValidException.
If Spring finds an ExceptionHandler method for this exception it will execute the code inside this method.
You just need to make sure that the method above is present in your Controller.
Now there is another case when you have multiple Controllers where you want to validate the method arguments. In this case I suggest you to create a ExceptionResolver class and put this method there. Make your Controllers extend this class and your job is done.
Try this
#RequestMapping(value = "/valid")
public String validatedMethod(#Valid ValidObject object, BindingResult result) {
StringBuilder builder = new StringBuilder();
List<FieldError> errors = result.getFieldErrors();
for (FieldError error : errors ) {
builder.append(error.getField() + " : " + error.getDefaultMessage());
}
return builder.toString();
}
When you use #Valid and doing bad request body Spring handle MethodArgumentNotValidException
You must create special class and extend ResponseEntityExceptionHandler and override handleMethodArgumentNotValid
Example
#ControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(UserExistException.class)
public ResponseEntity<Object> handleUserExistException(
UserExistException e, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.BAD_REQUEST.value());
body.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
body.put("message", e.getMessage());
body.put("path", request.getDescription(false).replace("uri=", ""));
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.BAD_REQUEST.value());
body.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
body.put("path", request.getDescription(false).replace("uri=", ""));
return new ResponseEntity<>(body, headers, status);
}
}
The answer by #dharam works.
For users at Spring v4.3, Here's a nice implementation which uses a Custom Exception class to handle exception by type.
#RestControllerAdvice
public class CustomExceptionClass extends ResponseEntityExceptionHandler{
#ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleException(MethodArgumentNotValidException ex, WebRequest req){
// Build your custom response object and access the exception message using ex.getMessage()
}
}
This method will enable handling all #Valid exceptions across all of your #Controller methods in a consolidated way
I am using Spring MVC with Controllers, my question is how do I return a JSON response which is different from the #ResponseBody object which is returned and convereted to a JSON to be returned.
To elaborate further, I have the object called "UserDetails" which has two fields called "name", "emailAddress"
#ResponseBody UserDetails
now the json returned will look like
{ name : "TheUsersName",
emailAddress:"abc#abc123.com" }
Is there any way I can modify the json before returning (ALL jsons in all methods across all controllers) where a "status" field will be added and the other json data will be under the "data" key in the json.
Also how do I return a json to the frontend when the java server from somewhere throws an exception, the json should have "status : false" and the exception name (atleast the status part though)
Create a response class:
public class Response<T> {
T data;
boolean status = true;
public Response(T d) { data = d; }
}
Then return that from your controllers:
#ResponseBody public Response getUserDetails) {
//...
return new Response(userDetails);
}
For the exception you'll want to return an object like:
public class BadStatus {
String errorMessage;
boolean status = false;
public BadStatus(String msg) { errorMessage = msg; }
}
#ExceptionHandler(Exception.class)
public BadStatus handleException(Exception ex, HttpServletRequest request) {
return new BadStatus(ex.getMessage());
}
Yes. Return a model and a view instead.
public ModelMap getUserDetails() {
UserDetails userDetails; // get this object from somewhere
ModelMap map = new ModelMap()(;
map.addAttribute("data", userDetails);
map.addAttribute("success", true);
return map;
}
To add the exception you'd do it the same way with a key and success = false.
An alternate solution (works with spring 3.1), which is less invasive
in your spring config :
<bean id="jacksonConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="mypackage.MyMessageConverter"
p:delegate-ref="jacksonConverter">
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
The idea is to provide your own HttpMessageConverter that delegates to the provided jackson converter.
public class MyMessageConverter implements HttpMessageConverter<Object> {
// setters and delegating overrides ommitted for brevity
#Override
public void write(Object t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
// t is whatever your #ResponseBody annotated methods return
MyPojoWrapper response = new MyPojoWrapper(t);
delegate.write(response, contentType, outputMessage);
}
}
This way all your pojos are wrapped with some other json that you provide there.
For exceptions, the solution proposed by ericacm is the simplest way to go (remember to annotate the 'BadStatus' return type with #ResponseBody).
A caveat : your json-serialized BadStatus goes through MyMessageConverter too, so you will want to test for the object type in the overriden 'write' method, or have MyPojoWrapper handle that.