1

I'm new to spring, so please excuse my ignorance. I'm trying to "return" a custom response when a given method parameter, which is "required", is null. the current response, from spring, is:

{
  "timestamp": 1477060294961,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.web.bind.MissingServletRequestParameterException",
  "message": "Required String parameter 'bookname' is not present",
  "path": "/getbook"
}

I am trying to get to a point where it "returns":

{
  "status": 400,
  "error": {
    // custom error body
  }
}

I thought a nice way of doing this would be to have a custom "parameter annotation". This would also make the code much more readable and store useful information about this endpoint parameter.

I followed the example given here, but I'm not sure where or how to return the custom response?

So far I have the annotation:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface customParameter {

String value() default "";
boolean required() default false;
String defaultValue() default ValueConstants.DEFAULT_NONE;
String customInfo() default  "blar blar";
}

the "endpoint" :

  @RequestMapping(value = "/getbook", method = {RequestMethod.POST})
  public ResponseEntity<BookResponse> getBookInfo(
      @customParameter(value = "bookname", required = true, customInfo = "extremely valuable book")
      final String bookname
   ) {
    return new bookRequest(bookname).getResponse;
}

and have an custom resolver :

public class CustomAnnotationResolver implements HandlerMethodArgumentResolver {


  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return parameter.getParameterAnnotation(customParameter.class) != null;
  }


  @Override
  public Object resolveArgument(MethodParameter methodparameter, ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

    CustomParameter customParameter = methodparameter.getParameterAnnotation(CustomParameter.class);

    String parameter = webRequest.getParameter(CustomParameter.value());

    // todo: do Validation here
    if (customParameter == null) {
      if (Parameter.required()) {
        String customInfo = customParameter.customInfo();
        String body getBody(customInfo);
        new ResponseEntity(body, 400); // so the problem is here!! how do I return this response??  
      }
    }

    return webRequest.getParameter(customParameter.value());

  }
}

I have also "registered" this resolver with the webConfig :

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

  @Override
  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    argumentResolvers.add(new CustomAnnotationResolver());
  }
}

any help with this implementation or any other suggestions of how to do this would be fantastic. Thanks all :)

Community
  • 1
  • 1
Luke_P
  • 659
  • 2
  • 7
  • 23
  • 2
    if you want to Handle de response easily you can use a trasnlation controller using @ControllerAdvice; this aproach catchs the rumtime exceptions what you want handle and create a custom error response like you want, based in a errorDTO objet with the attributes that you need to show. – johnnymnmonic Nov 17 '16 at 12:19
  • so should I throw an error in resolveArgument(){} and catch it how you suggested.? Could you provide example please, I'm a bit lost, as is clearly apparent. it looks like spring raps whatever I throw in a 500 error. – Luke_P Nov 17 '16 at 12:37
  • 1
    To customize the body for an error status you can return a custom java bean in the `@ExceptionHandler` of the `@ControllerAdvice`. You can look at [this little example](https://github.com/growlingchaos/update-msg/blob/custom-error/src/main/java/org/damage/rest/GeneralExceptionAdvice.java) I wrote. – growlingchaos Nov 17 '16 at 13:46

3 Answers3

3

Thank you @growlingchaos, brilliant, this is the solution.

@ControllerAdvice
@RestController
public class customExceptionAdvice {

  @ExceptionHandler(Exception.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public ResponseEntity handleConflict(BadRequestException e, HttpServletResponse response)
      throws IOException {

    return new ResponseEntity(e.getErrorBody(), HttpStatus.BAD_REQUEST);
  }
Luke_P
  • 659
  • 2
  • 7
  • 23
2

I be late, This is an simple example of how to catch rumtime error and translate the response in simple way.

TranslationController:

@ControllerAdvice
public class ExceptionTranslator {

private final Logger log = LoggerFactory.getLogger(ExceptionTranslator .class);

@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorDTO> processRuntimeException(HttpServletRequest req, RuntimeException ex) throws Exception {
    ErrorDTO errorDTO;
    ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
    if (responseStatus != null) {
        errorDTO = new ErrorDTO(req.getRequestURI(), responseStatus.value().toString(), ex.getMessage());
    } else {
        errorDTO = new ErrorDTO(req.getRequestURI(), HttpStatus.INTERNAL_SERVER_ERROR.toString(), ex.getMessage());
    }
    log.debug(errorDTO.toString());
    return new ResponseEntity(errorDTO, HttpStatus.valueOf(Integer.valueOf(errorDTO.getCode())));
}
}

DTO class to define attributes to show

public class ErrorDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    private final String uri;
    private final String code;
    private final String description;

    public ErrorDTO(String message) {
        this(null, message, null);
    }

    public ErrorDTO(String uri, String code, String description) {
        this.uri = uri;
        this.code = code;
        this.description = description;
    }

    public String getUri() {
       return uri;
    }

    public String getCode() {
       return code;
    }

    public String getDescription() {
        return description;
    }

    @Override
    public String toString() {
        return "ErrorDTO{" + "uri=" + uri + ", code=" + code + ", description=" + description + '}';
    }
 }
johnnymnmonic
  • 774
  • 8
  • 11
1

If you need custom MissingServletRequestParameterException handling I would add this to the controller or controller advice:

@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleMissingParam(MissingServletRequestParameterException e) {


    Map<String, Object> result = new HashMap();

    result.put("status", Integer.toString(400));

    Map<String, String> error = new HashMap();
    error.put("param", e.getParameterName());

    result.put("error", error);
    return result;
}

You can of course replace usage of Map by java objects. Personally I would advice you to use the original error handling unless you have a real reason for that.

ps-aux
  • 11,627
  • 25
  • 81
  • 128