Curl Spring complex object is null - spring

I am using Spring boot to develop a REST application.
Significant code looks like this:
Controller
#PostMapping(value = "/addUpdateNewsItem")
public ResponseEntity<Object> addUpdateNewsItem(
#RequestBody NimbusNewsDto dto,
Principal principal) {
the DTO:
public class NimbusNewsDto {
private String userDn;
private NimbusNewsJson newsItem;
NimbusNewsJson:
private String nimbusId;
private String subject;
private String description;
private String creator;
private String createDate;
private String expirationDate;
Curl:
curl -k -d #newsItem1.json -H "Content-Type: Application/json" http://localhost:8443/baseline/news/addUpdateNewsItem
the data:
{"newsDto":{
"userDn": "localhost",
"newsItem": {
"nimbusId": "nimbusId1",
"subject": "subject1",
"description": "Hello I am the first news item",
"creator": "God",
"createDate": "23/Jul/2020:02:15:11",
"expirationDate": ""
}
}}
I took a while to get past formatting errors causing 'bad request', etc.. Now when I make the call and debug the code I see that the dto object has the two items, both of which are null. I've tried multiple combinations of " and ' and escaped characters, all with no luck.
I'm running the Ubuntu shell under windows, and the spring code is running on Tomcat.
Suggestions? The last time I ran into this I ended up breaking up the DTO object into multiple parameters, but I rather just have the DTO as input.

the json you are sending does not "match" the dto, there is an extra object that "wraps" your dto.
you have to remove this part:
{
"newsDto": {
...
}
like this:
{
"userDn": "localhost",
"newsItem": {
"nimbusId": "nimbusId1",
"subject": "subject1",
"description": "Hello I am the first news item",
"creator": "God",
"createDate": "23/Jul/2020:02:15:11",
"expirationDate": ""
}
}

Related

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();
}

spring/hibernate validation -> error message is not passed to caller?

I am using org.springframework.boot:spring-boot-starter-validation:2.7.0(which in turn uses hibernate validator) to validate user input to rest controller.
I am using Spring Boot Web Starter (2.7.0) based project with #RestController annotation
My #GetMapping method is something like below -
#GetMapping(path = "/abcservice")
public Object abcService(
#RequestParam(value = "accountId", required = true) String accountId,
#Valid #RequestParam(value = "offset", required = false, defaultValue = "0") int offset,
#Valid #RequestParam(value = "limit", required = false, defaultValue = "10000") int limit
) throws Exception {
My problem is - I want the user to know about any input validation errors so they can correct and retry. But the framework is just giving 400 status code with below message.
{
"timestamp": "2022-08-03T16:10:14.554+00:00",
"status": 400,
"error": "Bad Request",
"path": "/somepath/abcservice"
}
On the server side the request is logged in warn.
2022-08-03 21:40:14.535 WARN 98866 --- [nio-8080-exec-1]
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved
[org.springframework.web.method.annotation.MethodArgumentTypeMismatchException:
Failed to convert value of type 'java.lang.String' to required type
'int'; nested exception is java.lang.NumberFormatException: For input
string: "0s"]
I want this above error message --> Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: "0s" also to be passed on to user. Is there a easy configuration based way to achieve.
I think i can add a ControllerAdvice to handle this exception and include this message in the response from handler method. But this will be a couple of lines of code. Is there an even simpler way than the ControllerAdvice approach.
Similarly if the client don't pass the mandatory accountId param, the client is just getting the same 400 response as above. No details or hints to the client about what wrong they are doing or how they can fix it.. but on the server side i can see below warn log.
2022-08-03 21:59:20.195 WARN 235 --- [nio-8080-exec-3]
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved
[org.springframework.web.bind.MissingServletRequestParameterException:
Required request parameter 'accountId' for method parameter type
String is not present]
I want the client to know about this error/exception. Nothing secret here to hide (atleast in my case).
Edit - found this config -
server.error.include-message=always
Now the issue is, bad requests are sent with 500 status code, I want them to sent as 400. Then this Q is solved.
Validations made by #Valid return with 500 Status Code. Is there anyway to tell the server to return 400 response when validations fail (without using ControllerAdvice).
If you wish to test-- you can try -->
Annotate controller with #Validated.
And execute below method and you will see 500 error but would want this to be 400.
#GetMapping("/test")
public void test(#Valid #RequestParam(value = "studentId", required = false)
#Min(value=0, message="Can not be less than 0") #Max(value=200, message="Can not be above 200") Long studentId ) {
System.out.println("hit: ");
}
And hit - http://localhost:9099/test?studentId=400
The spring in-built solution without global exception handler and with minimal config is by adding the below property in the application.properties.
server.error.include-binding-errors=always
The above property can have three values:
always ----> All api's in the app will always return well defined validation error message response.
on-param ----> All api's in the app will conditionally return well defined validation error message response based on input request param field "errors"
never ---> to disable this feature.
Example Github Project Reference
Demo Test:
package com.example.demo;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
#RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#PostMapping("/test")
public void test(#Valid #RequestBody Student student) {
System.out.println("studentData: " + student);
}
}
class Student {
#NotNull(message = "firstName cannot be null")
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#Override
public String toString() {
return "Student [firstName=" + firstName + ", lastName=" + lastName + "]";
}
}
Request:
{
"firstName": null,
"lastName" : "sai"
}
Response: (with HTTP response code = 400)
{
"timestamp": "2022-08-04T05:23:58.837+00:00",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotNull.student.firstName",
"NotNull.firstName",
"NotNull.java.lang.String",
"NotNull"
],
"arguments": [
{
"codes": [
"student.firstName",
"firstName"
],
"arguments": null,
"defaultMessage": "firstName",
"code": "firstName"
}
],
"defaultMessage": "firstName cannot be null",
"objectName": "student",
"field": "firstName",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotNull"
}
],
"path": "/test"
}
Use #expection handler and controller advice this help to handle your issue

Spring Web - 405 method not allowed

I recently tried to program a simple api in spring.
When I try it with postman, the only two working endpoints are the fetchAllMovie and the createMovie. The others (with request parameter) give a response:
{
"timestamp": "2021-11-30T14:38:34.396+00:00",
"status": 405,
"error": "Method Not Allowed",
"path": "/api/movies"
}
Here's a snippet:
#RestController
#RequestMapping("/api/movies")
public class MovieController {
#Autowired
private MovieService movieService;
#Autowired
private MovieRepository movieRepository;
#Autowired
private MovieMapper movieMapper;
#GetMapping
public List<Movie> fetchAllMovie() {
return movieService.getAllMovie();
}
#PostMapping
public MovieDto createMovie(#RequestBody MovieCreationDto movieCreationDto) {
Movie movie = movieMapper.creationDtoToModel(movieCreationDto);
return movieMapper.modelToDto(movieRepository.save(movie));
}
#GetMapping("/{movieId}")
public MovieDto fetchMovieById(#PathVariable("movieId") String movieId) throws MovieNotFoundException {
Movie movie = movieRepository.findById(movieId).orElseThrow(MovieNotFoundException::new);
return movieMapper.modelToDto(movie);
}
}
So if I send a GET request like http://localhost:8080/api/movies?movieId=619fa9d9b0c30252474b9a01 I get the error, but if I send a GET or POST request like http://localhost:8080/api/movies i can get all of the data from the data base or I can POST in it. (Of course with the proper request body)
Note it: Not only the GET req not working. Anything with request parameter gives me this error.
The #PathVariable is used to send parameter in path, like this: http://localhost:8080/api/movies/619fa9d9b0c30252474b9a01
If you want to send it using URL you specified, you need to use annotation #RequestParam
If you are using the #PathVariable as the input parameter, then you should call the endpoint in the following way:
http://localhost:8080/api/movies/619fa9d9b0c30252474b9a01
If you would like to use the #RequestParameter then call the api like this:
http://localhost:8080/api/movies?movieId=619fa9d9b0c30252474b9a01
Quick summary:
https://www.baeldung.com/spring-requestparam-vs-pathvariable

Spring Boot catch multiple exceptions and send as error response

I am validating an incoming POST request which will create a database entity after validating the request data. I am trying to gather multiple errors in a single request and respond as error response following JSON API spec:
https://jsonapi.org/examples/#error-objects-multiple-errors
HTTP/1.1 400 Bad Request
Content-Type: application/vnd.api+json
{
"errors": [
{
"status": "403",
"source": { "pointer": "/data/attributes/secretPowers" },
"detail": "Editing secret powers is not authorized on Sundays."
},
{
"status": "422",
"source": { "pointer": "/data/attributes/volume" },
"detail": "Volume does not, in fact, go to 11."
},
{
"status": "500",
"source": { "pointer": "/data/attributes/reputation" },
"title": "The backend responded with an error",
"detail": "Reputation service not responding after three requests."
}
]
}
Is it possible to do this by #ControllerAdvice. When Global exception handling is enabled by #ControllerAdvice and throws an exception, the next exception won't be caught.
Not directly, no. Not sure what is your business case/logic, therefore I don't know how you handling these exceptions in service layer, but in general, if you want to pass multiple errors in your #ExceptionHanlder - you could create a custom POJO:
public class MyError {
private String status;
private String source;
private String title;
private String detail;
getters/setters...
}
and then create a custom RuntimeException which would accept list of these POJOs:
public class MyRuntimeException extends RuntimeException {
private final List<MyError> errors;
public MyRuntimeException(List<MyError> errors) {
super();
this.errors = errors;
}
public List<MyError> getErrors() {
return errors;
}
}
And in your service layer you could create list of these POJOs, wrap then in your exception and throw it. Then in #ControllerAdvice you simply catch your exception and call accessor method to iterate against your list of POJOs to construct a payload you want.
Something like:
#ExceptionHandler (MyRuntimeException.class)
#ResponseStatus (BAD_REQUEST)
#ResponseBody
public Map<String, Object> handleMyRuntimeException(MyRuntimeException e) {
return singletonMap("errors", e.getErrors());
}

Error while executing post request in spring mvc

While Executing the following post request in postman:
http://localhost:8080/FinalSmsApi/rest/requestSms/hello
with parameter username,password and phone .
I am getting the following error :
HTTP Status 415: The server refused this request because the request entity is in a format not supported by the requested resource for the requested method
This is the controller:
#RestController
public class MainController1 {
#RequestMapping(value = "/hello", method = POST, consumes = "application/json")
public void Register(#RequestParam(value = "username") String username,
#RequestParam(value = "password") String password,
#RequestParam(value = "phone") String phone) {...}
}
Using Spring 4 version.
HTTP Status 415: The server refused this request...
This means that your endpoint is not able to process the passed Request Body. This error have two main reasons: either you did not specify what is the type of your request body or you passed an invalid data.
By Adding Content-Type header to your request headers, this problem would be solved:
Content-Type: application/json
And also, you're not capturing request body in your public void Register(..) method. If you're planning to go this way, it's better to drop the consumes attribute and pass all the parameters with Query Parameters, as you did.
The other approach is to define a resource class like:
public class User {
private String username;
private String password;
private String phone;
// getters and setters
}
Then change your controller to capture the request body, like following:
#RequestMapping(value = "/hello", method = POST, consumes = "application/json")
public void Register(#RequestBody User user) {...}
And finally, send a JSON representation along with your request:
curl -XPOST -H'Content-Type: application/json' --data '{"username": "", "password": "", "phone": ""}' http://localhost:8080/hello

Resources