Bad Request response with name of missing field - Spring Boot - java

I have an API endpoint that get a name and description parameters (both are mandatory)
createSomething(#RequestParam(value = "name") String name,#RequestParam(value = "description") String description)
If the client is not providing any of these he will get 400 Bad Request
Is there a way for me to tell the client which field is missing ? give more information for the "Bad Request" response
Update: Note that the parameters must be mandatory since I want that OpenAPI will detect that these parameters are mandatory. So solutions like making then "optional" and checking inside the body of the function is not what I am looking for

I see multiple answers but no one is specific enough.
1)
Spring by default has the capability of reporting in the error message what parameter was missing or other violations in the request.
However since spring boot version 2.3 the specific error messages are hidden, so that no sensitive information can be disclosed to the user.
You can use the property server.error.include-message: always which was the default mechanism before 2.3 version and allow spring to write error messages for you again.
2)
If you can't afford this because other sensitive info could be leaked from other exceptions, then you have to provide your own exception handler for this specific case
The following can be placed either in the same controller, or in another class marked with #ControllerAdvice
#ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity handleMissingParams(MissingServletRequestParameterException ex) {
return ResponseEntity.badRequest().body(String.format("Missing parameter with name:%s", ex.getParameterName()));
}

As #Shubam said, you can use the defaultValue attribute of #RequestParam annotation by setting the required attribute to true since both the parameters are mandatory.
Here is an example of how you could do it,
private final String DEFAULT_NAME = "Default Name";
private final String DEFAULT_DESC = "Default Desc";
#RequestMapping(value = "/get", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public ResponseEntity<String> createSomething(#RequestParam(required = true, name = "name", defaultValue = "Default Name") String name,
#RequestParam(required = true, name = "description", defaultValue = "Default Desc") String desc){
if(DEFAULT_NAME.equals(name)){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Field Name is missing");
}
if(DEFAULT_DESC.equals(desc)){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Field Desc is missing");
}
return ResponseEntity.ok(String.format("Hello, %s!",name));
}

You can use validation with a customised message :
#GetMapping("/name-for-month")
public String getNameOfMonthByNumber(#RequestParam #Min(1) #Max(value = 12, message = “month number has to be less than or equal to 12”) Integer month) {
// ...
}

There are many ways of handling errors for Rest find below a link of at least 5 solutions for your issue :
ExceptionHandler
HandlerExceptionResolver (ResponseStatusExceptionResolver this is the most adducate for your case or the 4th one if you use spring 5+)
ControllerAdvice
ResponseStatusException
Handle the Access Denied in Spring Security
https://www.baeldung.com/exception-handling-for-rest-with-spring

Since both parameters are mandatory you'll be getting 400 (bad request) if you try to send the request without paramters.
A workaround could be making request parameters non-mandatory(so that request can be sent without parameters) and provide a default value in case no parameter is provided
createSomething(#RequestParam(value = "name", required=false, defaultValue = null) String name,#RequestParam(value = "description", required=false, defaultValue = null) String description)
In the function, you can check for null like the following -
if (name == null) // name parameter is not provided
if (description == null) // description paramter is not provided
And, based on conditions you can also send error reponse if any one/more paramter not provided in the request.

Related

How to get information about HTTP request down the stack of the Spring framework handler method

Is there a way to get information about HTTP request from the method down the callstack of the Spring request handler method?
In other words given I have a handler method like:
#GetMapping("/hello")
public String hello(#RequestParam(value = "name", defaultValue = "World") String name) {
MyInternalClass.doSomeAction();
return String.format("Hello %s!", name);
}
I am looking for means to get the information about HTTP request (such as URL, headers, etc.) within the code of the doSomeAction() static method in the MyInternalClass class.
The constraint is that I cannot modify the original method (hello()).
You can add a Request parameter of type HttpServletRequest
#GetMapping("/hello")
public String hello(
#RequestParam(value = "name", defaultValue = "World") String name,
HttpServletRequest originalRequest) {
// HERE: call another method here
return String.format("Hello %s!", name);
}
Have a look at the Spring Reference Documentation, Chapter "Method Arguments"
Part 2
However, I was looking for a method that does not force developers to change their code. I will try to add an example to my question, so it will be more verbose.
You can use the RequestContextHolder to get the request attributes.
HttpServletRequest request =
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes())
.getRequest();
RequestContextHolder.getRequestAttributes() is a static method, that can be invoked from every where (even for a class that is no Spring Bean). But it is required that it is invoked from a thread that was triggert by a HTTP request.

Spring Boot Ambiguous handler error because of unvalidated URL parameters

I have two methods in my controller class with two unique URLs.
The first one has one optional parameter called name, which is a required parameter in the second URL. This seems to confuse Spring Boot when used.
First method:
#GetMapping(value = "/", params= {"id", "applicantid", "startingdate", "endingdate"})
public List<Event> getEventsById(#RequestParam("id") String sourceid,
#RequestParam("applicantid") String applicantid,
#RequestParam("startingdate") String startingdate,
#RequestParam("endingdate") String endingdate,
#RequestParam(value = "name", required = false) String name) {
The second one:
#GetMapping(value = "/", params= {"applicantid", "name", "startingdate", "endingdate"})
public List<Event> getEventsByApplicantId(#RequestParam("applicantid") String applicantid,
#RequestParam("name") String name,
#RequestParam("startingdate") String startingdate,
#RequestParam("endingdate") String endingdate) {
Now they both work fine, unless I add the optional parameter to the first URL like so:
/?id=999&applicantid=1&startingdate=2020&endingdate=2021&name=mobileapp
Spring Boot somehow thinks that I am trying to use the second method and it ignores the parameter "id" when parameter "name" is added, and gives me Ambiguous handler error. This is ofc because when "id" is ignored, the URL fits with the second one.
Shouldn't the "id" param be sufficient enough for Spring Boot to understand what method should be used?
I also noticed, that I could add all bunch of random parameters, and Spring Boot would always choose the second URL.
Do I need to validate the parameters, so that unknown parameters can't be added and Spring would not ignore the "id" parameter?

Whitelabel Error Page instead of missing required parameter

I have a GET /object call with required parameter id:
#RequestMapping(value = {"/object"}, produces = "application/json", method = RequestMethod.GET)
public String getObject(#RequestParam(required = true) String id, HttpServletRequest request) {
// do stuff
...
// Send message
return json;
}
When it's called without the parameter id my spring application throws a :
Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'id' is not present] but the caller recieves a Whitelabel Error Page with a 400 status with no explanation on the missing parameter...
Is there a way to return to the caller what paramter is missing ?
You need to create a custom Error Page, which would describe the exceptions you declare. See here: https://www.baeldung.com/exception-handling-for-rest-with-spring

How to handle invalid parameters in REST service?

I offer a REST webservice using spring #RestController.
How should in general invalid parameter content be handled? I tried throwing a custom exception, but that will lead to a HTTP 500 error on the client side and expose the stacktrace thereby.
Probably that is not the right way. But how should just simple error messages be returned? (the webservice will not be accessed manually by users. Just by other services connecting to the rest controller).
Im using jersey and this is a simple example that will use the hibernate bean validation framework to validate your beans. This is a work in progress but you should can see how it will work very simply.
#Path("customers")
public class CustomerResource {
#PUT
public Response createCustomer(Customer customer) {
BeanValidator.validate(customer);
final String rialtoId = customerProvider.createCustomer(customer);
return Response.ok(rialtoId).build();
}
}
Here is a generic class that I created that handles the bean validation.
public class BeanValidator {
/**
* Used to validate an order request and all the attached objects that
* support validation.
*
* #param request
* #throws ConstraintViolationException
*/
public static <T> void validate(T request) throws ConstraintViolationException {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<T>> constraintViolations = validator.validate(request);
if (constraintViolations.size() > 0) {
throw new ConstraintViolationException(new HashSet<ConstraintViolation<?>>(constraintViolations));
}
}
}
#XmlRootElement
public class Customer {
#NotNull(message = "spCustomerID1 is a required field")
#Size(max = 60, message = "spCustomerID1 has a max length of 60 characters")
private String spCustomerID1;
#Size(max = 60, message = "spCustomerID2 has a max length of 60 characters")
private String spCustomerID2;
#Size(max = 60, message = "spCustomerID3 has a max length of 60 characters")
private String spCustomerID3;
#NotNull(message = "customerName is a required field")
#Size(max = 60)
private String customerName;
#Valid
#NotNull(message = "customerAddress is a required field")
private PostalAddress customerAddress;
#Valid
#NotNull(message = "customerContact is a required field")
private ContactInfo customerContact;
#Valid
#NotNull(message = "technicalContact is a required field")
private ContactInfo technicalContact;
... / Getters and Setters
}
Then here is a simple ExceptionMapper that will support constructing a simple response to be sent back to the client. Notice that it will set the Response type to a 400 BAD_REQUEST instead of a 500+ Server Side error.
public class ConstraintViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
public Response toResponse(ConstraintViolationException exception) {
final StringBuilder strBuilder = new StringBuilder();
for (ConstraintViolation<?> cv : exception.getConstraintViolations()) {
strBuilder.append(cv.getPropertyPath().toString() + " " + cv.getMessage());
}
RestResponse responseEntity = RestResponse.responseCode(ResponseCode.CONSTRAINT_VIOLATION).setResponseMessage(strBuilder.toString()).build();
return Response.status(Response.Status.BAD_REQUEST).entity(responseEntity).build();
}
}
This code hasn't been tested yet but it might help to get some ideas of how to do the validation. This is a pretty straight forward way to do rest service validation in my opinion and allows you to report exact variable paths along with customized error messages for each field.
You should validate your parameters at the very outmost layer of your application before it gets handed off inside your domain. At this point you're still in the HTTP layer so can take the appropriate action which is to return a 400 BAD REQUEST status.
Within that you have complete control over how to relay this information to your users (or other services). Plain text is fine if you're just logging it, or design your own Json/Xml payload describing the error.
if i understand you well,
then , generally i think it's good to have a key in each json response (or even if your response is XML), that indecates the status of the process. this field could be called status.
so each and every response you send back should have this status field, and it's value should indicate what happens while processing, and what should the caller expect in the response.
the value could be a number, or a text message, some constant-like message
also you can add another field, message that contains some text-desc of the status code.
now you have to make a list of possible statues that your service may send back.
ex:
status: 0000
message: success
status: 0001
message: invalid_params
status: 0002
message: invalid_param_value
status: 0003
message: missing_param,
:
:
etc
so your json response will contain those fields always. among the other data supposed to be returned.
now it's the clients duty to handle those responses.
JSON example:
{
"status":"0000",
"message":"success",
"images":[ ... ]
}
{
"status":"0003",
"message":"missing_param"
}
as you notice in case of non 0000 status, no other data is sent back.
just telling the client we have "this issue".
or you can make it more informative, by adding : to the error message constant, telling more info about the error:
ex,
{
"status":"0003",
"message":"missing_param:album_id"
}
telling the user, there is a missing parameter, and it's album_id
now you can write all possible status responses, and there message
this will be part of your service documentation.
I you manually validate your arguments, you could throw a specific exception. Then you can map your exception to a specific HTTP status, for instance BAD REQUEST.
You can map your exception to a response status with Spring Controller Advice: http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc.

Annotating #FormParam fields with Swagger-UI #ApiParam

I have built a RestEasy API and linked it with Swagger UI. A task I have been asked to complete is to, find a way to reduce the query parameters in the method signature and handle them in some sort of "DTO".
My original implementation would be similar to:
#GET
#ApiOperation(value = "echo test value", notes = "echo test notes")
#ApiResponse(code = HttpServletResponse.SC_OK, message = "Response.status.OK")
public Response echoTest(
#ApiParam("id") #QueryParameter("id") final int id,
#ApiParam("fName") #QueryParameter("fName") final String fName,
#ApiParam("sName") #QueryParameter("sName") final String sName) {
// handle request
}
I have extracted the query-parameter handling to a DTO, although now I am unsure how to handle the Swagger-UI side of things. I have tried to annotate the fields in the DTO athough as I guessed, this did not work. My current solution without correct swagger-ui interaction:
#GET
#ApiOperation(value = "echo test value", notes = "echo test notes")
#ApiResponse(code = HttpServletResponse.SC_OK, message = "Response.status.OK")
public Response echoTest(#ApiParam("form") #FormParam QueryDTO dto) {
//Handle request
}
QueryDTO.java:
public class QueryDTO {
#ApiParam(name = "id", value = "user id") #QueryParam("id") private int id;
#ApiParam(name = "fName", value = "user first name") #QueryParam("fName") private String fName;
#ApiParam(name = "sName", value = "user surname") #QueryParam("sName) private String sName;
// Getters,setters etc
}
Does SwaggerUI support this type of feature? Is there an alternative approach I could take which would suit my use case? Any suggestions or help is appreciated, thanks.
The issue here isn't Swagger-UI but rather Swagger-Core.
Swagger-Core doesn't support RESTEasy's #Form annotation and only supports standard JAX-RS annotations.
I was unfamiliar with that annotation until you mentioned it, but it looks like it acts the same way as #BeanParam which was introduced in JAX-RS 2.0. Support for it should be provided with RESTEasy 3.0 and above. Swagger-core is able to process #BeanParam's in order to produce proper documentation.
If you still want just support for #Form, you'd have to open an issue on Swagger-Core's repository.

Categories