I'm building a Spring application which has #RestController, like:
#RestController
#RequestMapping(value = "/master")
public class MyController {
#Autowired
private MyService service;
#PostMapping("/call")
public ResponseEntity<Boolean> apiCall(#RequestBody MyDTO myDto) { ;
return new ResponseEntity<Boolean>(service.apiCall(myDto), OK);
}
}
And a request object:
public class MyDTO {
#JsonProperty("emp_number")
private long empNumber;
#JsonProperty("office_id")
private long officeId;
// ....constructors, etc.
}
In request json I want officeId to be not null.
So far I've tried marking the officeId field as:
#com.fasterxml.jackson.databind.annotation.JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
#JsonProperty(required = true)
#javax.validation.constraints.NotNull
But in the request json, even if I miss office_id, it is not throwing any error.
What am I missing?
It could be that in your case you are deserializing request, and missing primitive properties which are referenced by constructor are assigned a default value
java defaults
You could try to use corresponding Long wrapper object for MyDTO instead of primitives or maybe deserialization feature FAIL_ON_NULL_FOR_PRIMITIVES
You have to change the type from primitive to wrapper otherwise the default value of 0 will be considered and validation will pass.
Annotate MyDTO with #Valid annotation.
When Spring Boot finds an argument annotated with #Valid, it automatically bootstraps the default JSR 380 implementation — Hibernate Validator — and validates the argument.
from here
Please add following dependency to pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
PS: i have made minor adjustments to response structure to see the error in response
Entire code is as follows:
package com.example.spring.java.springjavasamples;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.Map;
#SpringBootApplication
public class SpringJavaSamplesApplication {
public static void main(String[] args) {
SpringApplication.run(SpringJavaSamplesApplication.class, args);
}
}
#RestController
#RequestMapping(value = "/master")
class MyController {
private final MyService service;
public MyController(MyService service) {
this.service = service;
}
#PostMapping("/call")
public ResponseEntity<Map<String, Object>> apiCall(#Valid #RequestBody MyDTO myDto, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
Map<String, String> errors = new HashMap<>();
bindingResult.getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return prepareResponse("error", errors, HttpStatus.BAD_REQUEST);
} else {
return prepareResponse("data", service.apiCall(myDto), HttpStatus.OK);
}
}
private ResponseEntity<Map<String, Object>> prepareResponse(String key, Object data, HttpStatus status) {
Map<String, Object> map = new HashMap<>();
map.put(key, data);
return new ResponseEntity<>(map, status);
}
}
class MyDTO {
#NotNull(message = "Employee number cannot be null")
#JsonProperty("emp_number")
private Long empNumber;
#NotNull(message = "Office Id cannot be null")
#JsonProperty("office_id")
private Long officeId;
#Override
public String toString() {
return "MyDTO{" +
"empNumber=" + empNumber +
", officeId=" + officeId +
'}';
}
}
#Service
class MyService {
public String apiCall(MyDTO myDto) {
System.out.println("all valid: " + myDto);
return myDto.toString();
}
}
Related
I have a simple Spring Boot project using spring-boot-starter-graphql. This project has one controller that accepts one argument.
#Controller
public class HelloNameController {
#QueryMapping
public String hello(#Argument String name) {
return "Hello " + name;
}
}
This argument is required.
Graphql schema
type Query {
hello (name : String!) : String
}
When I call this API in the Postman and do not pass this argument the app returns an error. I want to override the message of this error message, but I can't find a way to do it.
In the official documentation, it says to implement DataFetcherExceptionResolverAdapter and I've implemented it as a bean
#Configuration
public class GraphQLConfig {
#Bean
public DataFetcherExceptionResolver exceptionResolver() {
return DataFetcherExceptionResolverAdapter.from((ex, env) -> {
if (ex instanceof CoercingParseValueException) {
return GraphqlErrorBuilder.newError(env).message("CoercingParseValueException")
.errorType(ErrorType.ExecutionAborted).build();
}
if (ex instanceof CoercingSerializeException) {
return GraphqlErrorBuilder.newError(env).message("CoercingSerializeException")
.errorType(ErrorType.ExecutionAborted).build();
} else {
return null;
}
});
}
}
The problem is that the error never gets to this point. How do I catch this type of error and override the message?
I've asked a similar question on a GitHub. Responses from graphql-java project (#2866) and spring-graphql project (#415) were similar. To summarise at the time of writing it is not possible.
Then I've created a "workaround":
First, create a custom exception class that implements GraphQLError.
import graphql.GraphQLError;
import graphql.language.SourceLocation;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.http.HttpStatus;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
#Getter
#NoArgsConstructor
public class BadRequestException extends RuntimeException implements GraphQLError {
private HttpStatus status = HttpStatus.BAD_REQUEST;
private String message = "Resource not found";
// Below code used for GraphQL only
private List<SourceLocation> locations;
public BadRequestException(String message, List<SourceLocation> locations) {
this.message = message;
this.locations = locations;
}
#Override
public Map<String, Object> getExtensions() {
Map<String, Object> customAttributes = new LinkedHashMap<>();
customAttributes.put("errorCode", this.status.value());
return customAttributes;
}
#Override
public List<SourceLocation> getLocations() {
return locations;
}
#Override
public ErrorType getErrorType() {
return ErrorType.BAD_REQUEST;
}
#Override
public Map<String, Object> toSpecification() {
return GraphQLError.super.toSpecification();
}
}
Second, create an interceptor class that implements WebGraphQlInterceptor and annotate it as #Component, so Spring can create it as a bean. Inside this class implement logic to catch the needed error and convert it to the exception class created before
import graphql.ErrorClassification;
import graphql.ErrorType;
import graphql.GraphQLError;
import graphql.validation.ValidationErrorType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.graphql.ResponseError;
import org.springframework.graphql.server.WebGraphQlInterceptor;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.stream.Collectors;
#Slf4j
#Component
public class ErrorInterceptor implements WebGraphQlInterceptor {
#Override
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
return chain.next(request)
.map(response -> {
log.info("[ErrorInterceptor] Intercepting response... ");
List<GraphQLError> graphQLErrors = response.getErrors().stream()
.filter(error -> ErrorType.ValidationError.equals(error.getErrorType()))
.map(this::resolveException)
.collect(Collectors.toList());
if (!graphQLErrors.isEmpty()) {
log.info("[ErrorInterceptor] Found invalid syntax error! Overriding the message.");
return response.transform(builder -> builder.errors(graphQLErrors));
}
return response;
});
}
private GraphQLError resolveException(ResponseError responseError) {
ErrorClassification errorType = responseError.getErrorType();
if (ErrorType.ValidationError.equals(errorType)) {
String message = responseError.getMessage();
log.info("[ErrorInterceptor] Returning invalid field error ");
if (ValidationErrorType.NullValueForNonNullArgument.equals(
extractValidationErrorFromErrorMessage(responseError.getMessage()))) {
String errorMessage =
"Field " + StringUtils.substringBetween(message, "argument ", " #") + " cannot be null";
return new BadRequestException(errorMessage, responseError.getLocations());
}
}
log.info("[ErrorInterceptor] Returning unknown query validation error ");
return new BadRequestException("Unknown error", responseError.getLocations());
}
private ValidationErrorType extractValidationErrorFromErrorMessage(String message) {
return ValidationErrorType.valueOf(StringUtils.substringBetween(message, "type ", ":"));
}
}
The only problem with this approach is that all needed information like a type of an error, the field that causes the error, etc. is embedded in the native error message. So to extract the needed parameters we have to parse the string message.
I am working on my first Spring-Boot app. Got a working UI Controller implemented below:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;
import java.util.List;
import java.util.ArrayList;
#Controller
public class UiController {
private ProductService productService;
private LocationService locationService;
private InventoryService inventoryService;
private CartService cartService;
public UiController(
ProductService productService,
LocationService locationService,
InventoryService inventoryService,
CartService cartService) {
this.productService = productService;
this.locationService = locationService;
this.inventoryService = inventoryService;
this.cartService = cartService;
}
#GetMapping("/")
public String home(Model model) {
model.addAttribute("products", productService.getAllProducts());
return "index";
}
#GetMapping("/brand/{brand}")
public String brand(Model model, #PathVariable String brand) {
List prods = productService.getProductByBrand(brand);
if (prods.size() == 0) throw new ItemNotFoundException();
model.addAttribute("products", prods);
return "index";
}
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such item") // 404
public class ItemNotFoundException extends RuntimeException {
// ...
}
#GetMapping("/product/{productId}")
public String product(Model model, #PathVariable String productId) {
Product prod = productService.getProduct(productId);
if (prod == null) throw new ItemNotFoundException();
ArrayList<Product> ps = new ArrayList<Product>();
ps.add(prod);
model.addAttribute("products", ps);
return "index";
}
}
I want to add a REST controller returning the same thing as HTML only I want the responses to be in JSON. When there is no data, I want an error return. Added the below:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.HttpStatus;
import java.util.List;
import java.util.ArrayList;
import com.google.gson.Gson;
#ApiController
public class ApiController {
private ProductService productService;
private LocationService locationService;
private InventoryService inventoryService;
private CartService cartService;
public ApiController(
ProductService productService,
LocationService locationService,
InventoryService inventoryService,
CartService cartService) {
this.productService = productService;
this.locationService = locationService;
this.inventoryService = inventoryService;
this.cartService = cartService;
}
#GetMapping("/rest")
public String home() {
List prods = productService.getAllProducts();
if (prods.size() == 0) throw new ItemNotFoundException();
return new Gson().toJson(prods);
}
#GetMapping("/rest/brand/{brand}")
public String brand(#PathVariable String brand) {
List prods = productService.getProductByBrand(brand);
if (prods.size() == 0) throw new ItemNotFoundException();
return new Gson().toJson(prods);
}
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such item") // 404
public class ItemNotFoundException extends RuntimeException {
// ...
}
#GetMapping("/rest/product/{productId}")
public String product(#PathVariable String productId) {
Product prod = productService.getProduct(productId);
if (prod == null) throw new ItemNotFoundException();
return new Gson().toJson(prod);
}
}
Apparently, autoconfig is working and my controller gets picked by the compiler. Only, I get the below error:
Compilation failure
ApiController.java:[21,2] incompatible types: com.rei.interview.ui.ApiController cannot be converted to java.lang.annotation.Annotation
What am I doing wrong and what should I do?
You did a simple mistake at the beginning of the controller.
The class must be annotated #RestController... not #ApiController
Change your code from
#ApiController
public class ApiController {
...
}
to
#RestController // <- Change annotation here
public class ApiController {
...
}
The error
ApiController.java:[21,2] incompatible types:
com.rei.interview.ui.ApiController cannot be converted to java.lang.annotation.Annotation
informs you that the annotation #ApiController is not a of type java.lang.annotation.Annotation
I'm trying to centralize the error handling in my spring boot app. Currently i'm only handling one potential exception (NoSuchElementException), this is the controller advice:
import java.util.NoSuchElementException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(NoSuchElementException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public DispatchError dispatchNotFound(NoSuchElementException exception) {
System.out.println("asdasdasd");
return new DispatchError(exception.getMessage());
}
}
And here's the service which throws the exceptions:
import java.util.List;
import com.deliveryman.deliverymanapi.model.entities.Dispatch;
import com.deliveryman.deliverymanapi.model.repositories.DispatchRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class DaoService {
#Autowired
DispatchRepository dispatchRepo;
public Dispatch findByShipmentNumber(long shipmentNumber) {
return dispatchRepo.findById(shipmentNumber).orElseThrow();
}
public List<Dispatch> findByUser(String user, String status) {
if(status == null) {
return dispatchRepo.findByOriginator(user).orElseThrow();
} else {
return dispatchRepo.findByOriginatorAndStatus(user, status).orElseThrow();
}
}
public Dispatch createDispatch(Dispatch dispatch) { //TODO parameter null check exception
return dispatchRepo.save(dispatch);
}
}
The problem is that once I send a request for an inexistent resource, the json message shown is the spring's default one. It should be my custom json error message (DispatchError).
Now, this is fixed by adding a #ResponseBody to the exception handler method but the thing is that I was using an old code of mine as reference, which works as expected without the #ResponseBody annotation.
Can someone explain me why this is happening?
Either annotate your controller advice class with #ResponseBody
#ControllerAdvice
#ResponseBody
public class ExceptionController {
...
or replace #ControllerAdvice with #RestControllerAdvice.
Tested and verified on my computer with source from your controller advice.
From source for #RestControllerAdvice
#ControllerAdvice
#ResponseBody
public #interface RestControllerAdvice {
...
Hence, #RestControllerAdvice is shorthand for
#ControllerAdvice
#ResponseBody
From source doc for #ResponseBody
Annotation that indicates a method return value should be bound to the
web response body. Supported for annotated handler methods.
Alternative using #ControllerAdvice only:
#ControllerAdvice
public class ExceptionHandlerAdvice {
#ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<DispatchError> dispatchNotFound(NoSuchElementException exception) {
return new ResponseEntity<>(new DispatchError(exception.getMessage()), HttpStatus.NOT_FOUND);
}
}
I do have a theory on what's going on in your old app. With the advice from your question, and the error handler below, I can create a behaviour where the DispatchError instance appears to be returned by advice (advice is executed), but is actually returned by error controller.
package no.mycompany.myapp.error;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
#RestController
#RequiredArgsConstructor
public class ErrorHandler implements ErrorController {
private static final String ERROR_PATH = "/error";
private final ErrorAttributes errorAttributes;
#RequestMapping(ERROR_PATH)
DispatchError handleError(WebRequest webRequest) {
var attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE));
return new DispatchError((String) attrs.get("message"));
}
#Override
public String getErrorPath() {
return ERROR_PATH;
}
}
Putting an implementation of ErrorController into classpath, replaces Spring's BasicErrorController.
When reinforcing #RestControllerAdvice, error controller is no longer in effect for NoSuchElementException.
In most cases, an ErrorController implementation that handles all errors, in combination with advice exception handlers for more complex exceptions like MethodArgumentNotValidException, should be sufficient. This will require a generic error DTO like this
package no.mycompany.myapp.error;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.Map;
#Data
#NoArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiError {
private long timestamp = new Date().getTime();
private int status;
private String message;
private String url;
private Map<String, String> validationErrors;
public ApiError(int status, String message, String url) {
this.status = status;
this.message = message;
this.url = url;
}
public ApiError(int status, String message, String url, Map<String, String> validationErrors) {
this(status, message, url);
this.validationErrors = validationErrors;
}
}
For ErrorHandler above, replace handleError with this
#RequestMapping(ERROR_PATH)
ApiError handleError(WebRequest webRequest) {
var attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE));
return new ApiError(
(Integer) attrs.get("status"),
(String) attrs.get("message"), // consider using predefined message(s) here
(String) attrs.get("path"));
}
Advice with validation exception handling
package no.mycompany.myapp.error;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.stream.Collectors;
#RestControllerAdvice
public class ExceptionHandlerAdvice {
private static final String ERROR_MSG = "validation error";
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
ApiError handleValidationException(MethodArgumentNotValidException exception, HttpServletRequest request) {
return new ApiError(
HttpStatus.BAD_REQUEST.value(),
ERROR_MSG,
request.getServletPath(),
exception.getBindingResult().getFieldErrors().stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage,
// mergeFunction handling multiple errors for a field
(firstMessage, secondMessage) -> firstMessage)));
}
}
Related config in application.yml
server:
error:
include-message: always
include-binding-errors: always
When using application.properties
server.error.include-message=always
server.error.include-binding-errors=always
When using Spring Data JPA, consider using the following setting for turning off a second validation.
spring:
jpa:
properties:
javax:
persistence:
validation:
mode: none
More information on exception handling in Spring:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc (revised April 2018)
https://www.baeldung.com/exception-handling-for-rest-with-spring (December 31, 2020)
issue is when i am looking swagger for v1 there i can see one endpoint which is valid, but for v2 i have given two endpoints inside controller, but /allusers endpoint i am not able to see. below are the controller.
controller v1:
package com.springboot.rest.controller.v1;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springboot.rest.dto.UserDto;
import com.springboot.rest.service.UserService;
#RestController(value = "userControllerV1")
#RequestMapping(value = "/userinfo", produces = "application/json")
public class UserController {
public static final String X_ACCEPT_VERSION_V1 = "X-Accept-Version" + "=" + "v1";
#Autowired
private UserService userService;
#GetMapping(value = "/allusers", headers = X_ACCEPT_VERSION_V1)
public List<UserDto> getUserinfo() {
List<UserDto> finalResults = userService.getAllUserInfo();
return finalResults;
}
}
controller v2:
package com.springboot.rest.controller.v2;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springboot.rest.dto.UserDto;
import com.springboot.rest.service.UserService;
#RestController(value = "userControllerV2")
#RequestMapping(value = "/userinfo", produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController {
public static final String X_ACCEPT_VERSION_V2 = "X-Accept-Version" + "=" + "v2";
#Autowired
private UserService userService;
#GetMapping(value = "/allusers", headers = X_ACCEPT_VERSION_V2)
public List<UserDto> getUserinfo() {
List<UserDto> finalResults = userService.getAllUserInfo();
return finalResults;
}
#GetMapping(value = "/message", headers = X_ACCEPT_VERSION_V2)
public String greetMessage() {
return userService.getGreetMessage();
}
}
and i don't want to change my getUserinfo() method, could anyone help?
URI paths for /allusers end point are same in both the controllers where as api endpoints should be unique through out the application. You can add version in the uri which will make it unique. For eg.
#RequestMapping(value = "/v2/userinfo", produces = MediaType.APPLICATION_JSON_VALUE)
I did many ways, but finally OpenApi and adding filter did it for me. below is the OpenApiConfig file and link for those who wants to achieve this.
#Configuration
public class OpenApiConfig {
#Bean
public OpenAPI customOpenApi() {
return new OpenAPI()
.components(new Components())
.info(new Info().title("User-Management Microservice")
.description("demo-microservice for user-management")
.termsOfService("www.abc.com")
.contact(new io.swagger.v3.oas.models.info.Contact()
.email("abc.com")
.name("user-management"))
.version("1.0"));
}
#Bean
public GroupedOpenApi v1OpenApi() {
String[] packagesToScan = {"com.springboot.rest.controller.v1"};
return GroupedOpenApi.builder().setGroup("v1 version").packagesToScan(packagesToScan).build();
}
#Bean
public GroupedOpenApi v2OpenApi() {
String[] packagesToScan = {"com.springboot.rest.controller.v2"};
return GroupedOpenApi.builder().setGroup("v2 version").packagesToScan(packagesToScan).build();
}
}
use below link for step by step explanation:
https://www.youtube.com/watch?v=Z4FwdCgik5M
I have tried a number of examples from the net and cannot get Spring to validate my query string parameter. It doesn't seem execute the REGEX / fail.
package my.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import javax.validation.constraints.Pattern;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
#RestController
public class MyController {
private static final String VALIDATION_REGEX = "^[0-9]+(,[0-9]+)*$";
#RequestMapping(value = "/my/{id}", method = GET)
public myResonseObject getMyParams(#PathVariable("id") String id,
#Valid #Pattern(regexp = VALIDATION_REGEX)
#RequestParam(value = "myparam", required = true) String myParam) {
// Do Stuff!
}
}
Current behaviour
PASS - /my/1?myparam=1
PASS - /my/1?myparam=1,2,3
PASS - /my/1?myparam=
PASS - /my/1?myparam=1,bob
Desired behaviour
PASS - /my/1?myparam=1
PASS - /my/1?myparam=1,2,3
FAIL - /my/1?myparam=
FAIL - /my/1?myparam=1,bob
Thanks
You need add #Validated to your class like this:
#RestController
#Validated
class Controller {
// ...
}
UPDATE:
you need to configure it properly.. add this bean to your context:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
Example to handle exception:
#ControllerAdvice
#Component
public class GlobalExceptionHandler {
#ExceptionHandler
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Map handle(MethodArgumentNotValidException exception) {
return error(exception.getBindingResult().getFieldErrors()
.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.toList()));
}
#ExceptionHandler
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Map handle(ConstraintViolationException exception) {
return error(exception.getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList()));
}
private Map error(Object message) {
return Collections.singletonMap("error", message);
}
}
You can try this
#Pattern(regexp="^[0-9]+(,[0-9]+)*$")
private static final String VALIDATION_REGEX;
(pay attention for the final modifier)
or else
#Pattern()
private static final String VALIDATION_REGEX = "^[0-9]+(,[0-9]+)*$";
And then remove #Pattern(regexp = VALIDATION_REGEX) from your method and keep only the #Valid annotation:
public myResonseObject getMyParams(#PathVariable("id") String id, #Valid #RequestParam(value = "myparam", required = true) String myParam) {