How to handle Bad Request to return custom Response object for PostRequest in Spring Boot Rest Api - spring-boot

I am Trying to create Api which can accept POST Request.
But I want to handle Bad Request Also.
My Json object will be like
JSON Object
{
"name": "tom"
"description" : "he is scholar"
}
JSON object with incorrect parameter
{
"name": "tom"
"descr" : "he is scholar"
}
#PostMapping("/questions")
public question addQuestion(#RequestBody question theQuestion) {
theQuestion.setId(0);
try {
thequestionService.save(theQuestion);
}catch(Exception ex) {
throw new badRequestException("bad request");
}
return theQuestion;
}
For Bad Request it is throwing spring Boot internel error.
But I want to send custom JSON object to back to client.
Response in case of failure 400 Bad Request.
{
"status":"failed to query"
"description" : " can be any thing"
}
Help me out for this issue.

You can use #JsonAlias annotation.
public class Question {
#JsonAlias(value={"description", "descr"})
private String description;
}
This annotation was added in Jackson 2.9. You might have to upgrade/override Jackson version.
For Bad Request it is throwing spring Boot internel error.
You can also add #JsonIgnoreProperties(ignoreUnknown = true) annotation to Question class. Doing this will not throw error for descr key in JSON, but will initialize description field in Question object as null.

Related

Error Response body changed after Boot 3 upgrade

I have the following Controller endpoint in my project:
#GetMapping(value = "/{id}")
public FooDto findOne(#PathVariable Long id) {
Foo model = fooService.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
return toDto(model);
}
My application retrieved the following response when it couldn't find a Foo with the provided id:
{
"timestamp": "2023-01-06T08:43:12.161+00:00",
"status": 404,
"error": "Not Found",
"path": "/foo/99"
}
However, after upgrading to Boot 3, the response body changed to:
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"instance": "/foo/99"
}
I couldn't find any related information in the Spring Boot 3.0 Migration Guide nor in the Upgrading to Spring Framework 6.x one.
Spring Web 6 introduced support for the "Problem Details for HTTP APIs" specification, RFC 7807.
With this, the ResponseStatusException now implements the ErrorResponse interface and extends the ErrorResponseException class.
Having a quick look at the javadocs, we can see that all these are backed by the RFC 7807 formatted ProblemDetail body, which, as you can imagine, has the fields of the new response you're getting, and also uses the application/problem+json media type in the response.
Here is a reference to how Spring now treats Error Responses, which naturally goes in the direction of using Problem Details spec across the board.
Now, normally, if you were simply relying on Boot's Error Handling mechanism without any further change, you would still see the same response as before. My guess is that you are using a #ControllerAdvice extending ResponseEntityExceptionHandler. With that, you are enabling RFC 7807 (as per the Spring docs here)
So, that is why your ResponseStatusException has changed its body content.
Configuring the Problem Detail response body to include previous fields
If you need to stick to the pre-existing fields (at least until you fully migrate to the Problem Detail based approach) or if you simply want to add custom fields to the error response, you can override the createResponseEntity method in the #ControlAdvice class extending ResponseEntityExceptionHandler as follows:
#ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> createResponseEntity(#Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
if (body instanceof ProblemDetail) {
ProblemDetail problemDetail = ((ProblemDetail) body);
problemDetail.setProperty("error", problemDetail.getTitle());
problemDetail.setProperty("timestamp", new Date());
if (request instanceof ServletWebRequest) {
problemDetail.setProperty("path", ((ServletWebRequest) request).getRequest()
.getRequestURI());
}
}
return new ResponseEntity<>(body, headers, statusCode);
}
}
Note: I'm using new Date() instead of Java Time simply because that's what Boot's DefaultErrorAttributes class uses. Also, I'm not using Boot's ErrorAttributes for simplicity.
Note that defining the path field is a little bit tricky because problemDetail.getInstance() returns null at this stage; the framework sets it up later in the HttpEntityMethodProcessor.
Of course, this solution is suitable for a servlet stack, but it should help figure out how to proceed in a reactive stack as well.
With this, the response will look as follows:
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"instance": "/foo/99",
"error": "Not Found",
"path": "/foo/99",
"timestamp": "2023-01-06T10:00:20.509+00:00"
}
Of course, it has duplicated fields. You can completely replace the response body in the method if you prefer.
Configuring Boot to also use the Problem Detail spec
Now, to be consistent across the board in your application, note that Boot now provides the spring.mvc.problemdetails.enabled property to use the Problem Details in its Error Handling mechanism (which is disabled by default to avoid breaking changes, as its associated issue):
spring.mvc.problemdetails.enabled=true

Why do I get random Http 404 from server between same requests with only one change in any field?

I haven an endpoint POST /api/marketplace/add that accepts a DTO object as request body. When I send the body below with platformName field set , server accepts request and processes it with no problem. But when I only try to change field platformName to null I get Http 404 error from server. I debugged the request and found out that it even can not reach controller method. I also got no trace from that error. What might be the cause that makes API respond differently to same request?
below
{
"platformName": "Trendyol",
"commissionAmounts": [
{
"amount": 23.45,
"categoryInfos": [
{
"categoryName": "Game"
}
],
"isCategoryBasedPricing": true
}
],
"shipmentAmounts": [
{
"amount": 23.45,
"scaleInfo": {
"order": 0,
"lowerBound": 0,
"upperBound": 0
},
"volumeInfo": {
"order": 0,
"lowerBound": 0,
"upperBound": 0
},
"isVolumeBasedPricing": true
}]
}
EDIT: dto model is
#Generated
public class MarketPlaceDTO {
#JsonProperty("platformName")
private String platformName;
#JsonProperty("commissionAmounts")
#Valid
private List<CommissionInfoDTO> commissionAmounts = new ArrayList<>();
#JsonProperty("shipmentAmounts")
#Valid
private List<ShipmentInfoDTO> shipmentAmounts = new ArrayList<>();
Controller is implementing swagger generated api interface. with postmapping and requestbody annotations.
#RequiredArgsConstructor
#RestController
public class MarketPlaceApiController implements MarketplaceApi {
private final MarketPlaceDAOService marketPlaceDAOService;
#Override
public ResponseEntity<BaseResponseDTO> addMarketPlace(MarketPlaceDTO
marketPlaceDTO) {
BaseResponseDTO dto =
marketPlaceDAOService.addMarketPlace(marketPlaceDTO);
return ResponseEntity.ok(dto);
}
}
Swagger generated api interface
#RequestMapping(
method = RequestMethod.POST,
value = "/marketplace/add",
produces = { "application/json", "application/xml" },
consumes = { "application/json" })
default ResponseEntity<BaseResponseDTO> _addMarketPlace(
#Parameter(name = "MarketPlaceDTO", description = "Add new
marketplace with given request body", required = true) #Valid
#RequestBody MarketPlaceDTO marketPlaceDTO) {
return addMarketPlace(marketPlaceDTO);
}
Response is
{
"timestamp": 1666866382906,
"status": 404,
"error": "Not Found",
"path": "/marketplace/add"
}
Obviously, that you use an endpoint with #RequestBody where body is a DTO.
And on trying to call this endpoint Spring Web first should match that a model in your request payload matches a require object in #RequestBody argument.
Ideally, using DTO as a request model is not a good idea. But I don't see your structure and cannot say if it's a problem or not.
The simple solution in your case is preparation (annotating) your DTO with specific JSON annotations:
#JsonInclude
#JsonIgnoreProperties(ignoreUnknown = true)
public class YourDTO {
private String platformName;
}
and for Controller add class annotation #Validated; for #RequestBody add #Valid annotation.
Recommendation: use request models for incoming objects, and later converters to DTO/entities with ability to response them with filtering (or in complex cases add also response model - usually it's overhead).
My problem was global exception handler component annotated with #ControllerAdvice. I tried to handle validation exceptions and forgot to add #ResponseBody to my handler methods which is in my case probabaly required. That somehow caused server to send http 404 message when any input validation exception was thrown. After I made changes , Exceptions was handled correctly by handler component.
#ControllerAdvice
#ResponseBody // this resolved my issue.
public class MVCExceptionHandler {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseErrorResponse
methodArgumentExceptions(MethodArgumentNotValidException e){
return BaseErrorResponse.builder()
.errorMessage(AppError.INVALID_OR_MISSING_USER_INPUT.getErrorMessage())
.errorCode(AppError.INVALID_OR_MISSING_USER_INPUT.getErrorCode())
.errorTime(Date.from(Instant.now())).build();
}

How to display validation error messages and codes in a Spring WebFlux Rest API

I'm using Spring Web MVC in a Reactive SpringBoot Application, and wrote a custom validator. In case of a validation error, a 400 response code is used, which is fine, but then AbstractErrorWebExceptionHandler catches this and spits out
{
"timestamp": 1651678946524,
"path": "/signup",
"status": 400,
"error": "Bad Request",
"requestId": "0f61cb96-1"
}
which is not very useful to the client. How can I display the error messages and codes? I know they are available, because they are logged:
DEBUG 32475 --- [ parallel-1] a.w.r.e.AbstractErrorWebExceptionHandler : [0f61cb96-1] Resolved [WebExchangeBindException: Validation failed for argument at index 0 in method: public reactor.core.publisher.Mono<...with 1 error(s): [Field error in object 'userSignup' on field 'username': rejected value [matilda0]; codes [username.exists.userSignup.username,username.exists.username,username.exists.java.lang.String,username.exists]; arguments []; default message [null]] ] for HTTP POST /signup
Any help would be appreciated, thank you.
This is what I did:
private void validate(SomeEntity someEntity) {
Errors errors = new BeanPropertyBindingResult(someEntity, "SomeEntity");
validator.validate(someEntity, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString());
}
}
Validator is injected:
private final Validator validator;
private final SomeEntityDao dao;
public SomeEntityHandler(
Validator validator,
SomeEntityDao dao
) {
this.validator = validator;
this.dao = dao;
}
Project: WebFluxR2dbc
Adding
server.error.include-binding-errors=ALWAYS
in application.properties seems to fix it, although it still doesn't look up the message code properly. To actually get the error message itself to appear in the response, I had to wire in a MessageSourceAccessor in my Validator and use that as the default message!
errors.rejectValue("username","username.exists",msgs.getMessage("username.exists"))
So I must still be missing something, but this will work for now.

Request method 'POST' not supported in Postman

I have created this controller
#PostMapping(value = "/validate")
public ResponseEntity<GenericResponse> validate(#RequestBody #Valid FormulaDTO formulaDto) {
System.out.println(formulaDto);
String expression = formulaDto.getFormula();
service.validate(expression);
return new ResponseEntity<>(new GenericResponse(HttpStatus.CREATED.value(), "Expression Validated"),
HttpStatus.CREATED);
}
The FormulaDTO looks like
public class FormulaDTO {
private String formula;
}
I have created a PostRequest from postman whose body contains the formulaText.
The api call from postman looks like
https://localhost:8443/validate
with body as
{
"formula" :"M00212=curr_month:M00213-curr_month:M00211*100.00"
}
I am getting the following as output with 500 Internal Server Error
{
"timestamp": "2021-05-03",
"message": "Request method 'POST' not supported",
"details": "uri=/error"
}
How can i use PostMapping?
Use http instead of https in postman.
Instead of this: https://localhost:8443/validate
Use this: http://localhost:8443/validate
Looking at the comments
There is no root level mapping at #RestController .
Even if you don't have a root level mapping on controller it seems that spring boot always needs the following annotation on controller level
#RequestMapping
Add it and it will be fixed!

Spring boot application does not allow get request

I followed tutorial from here (https://medium.com/echoenergy/how-to-use-java-high-level-rest-client-with-spring-boot-to-talk-to-aws-elasticsearch-9e12571df93e) to create a springboot- elastic search application.
I was able to do a successful POST and PUT method but GET request fails for
me ( using PostMan).
GET fails with following exception
{
"timestamp": "2019-03-09T10:45:18.496+0000",
"status": 405,
"error": "Method Not Allowed",
"message": "Request method 'GET' not supported",
"path": "/api/v1/profiles/464d06e8-ef57-49f3-ac17-bd51ba7786e2"
}
But I correctly added the corresponding get method in the controller
#RestController("/api/v1/profiles")
public class ProfileController {
private ProfileService service;
#Autowired
public ProfileController(ProfileService service) {
this.service = service;
}
#PostMapping
public ResponseEntity createProfile(
#RequestBody ProfileDocument document) throws Exception {
return
new ResponseEntity(service.createProfile(document), HttpStatus.CREATED);
}
#GetMapping("/{id}")
public ProfileDocument findById(#PathVariable String id) throws Exception {
return service.findById(id);
}
}
In the response, I can see that it allows only PUT and POST. But I could not find any config file in the server to explicitly add http methods other than the controller
Can someone please help
The issue with your controller that I can see is, there's no #RequestMapping("/api/v1/profiles") at controller class level. It should be like
#RestController
#RequestMapping("/api/v1/profiles")
You cannot specify the request path in #RestController's value field. It means (as per javadocs);
The value may indicate a suggestion for a logical component name, to
be turned into a Spring bean in case of an autodetected component.
Hope this helps.

Resources