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

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

Related

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 Squiggly library intercept and modify request parameter before reaching controller

I want to filters the attributes being sent in json response for multiple requests. I used Squiggly library to achieve this. Below is the config level code added to achieve this.
#Configuration
#ConditionalOnClass(ObjectMapper.class)
public class SquigglyAutoconfigure {
#Bean
public FilterRegistrationBean squigglyRequestFilter(ObjectMapper objectMapper)
{
Squiggly.init(objectMapper, new RequestSquigglyContextProvider());
FilterRegistrationBean<SquigglyRequestFilter> filter = new FilterRegistrationBean<>();
filter.setFilter(new SquigglyRequestFilter());
filter.setOrder(1);
return filter;
}
}
Now I have a working code that responds only the fields sent in query parameters as "fields". Eg - inventory/stock?fields=stockInformation will only return field stockInformation in response.
Now, front-end can send query prameter "fields" to receive only required fields.
But I also want to define a default set of fields for each request. i.e. if front-end do not sent queryparam "fields", i should be able to initialise fields with custom values. I tried adding defaultValue in contoller but it didn't worked.
Below code didn't worked
#PostMapping
#ResponseStatus(HttpStatus.OK)
public StockInformation returnAllStock(#RequestBody FilterDataList filterDataList,
#RequestParam(name = "fields", required = false,defaultValue="stockInformation{productId}") String fields)
Is there a way where I can intercept each request and initialise request param "fields"?
If you intialize RequestSquigglyContextProvider, you should be able to acheive this
The key is the new RequestSquigglyContextProvider("fields", "id,name,caption")
Example code
Squiggly.init(mapper, new RequestSquigglyContextProvider("fields", "id,name,caption") {
#Override
protected String customizeFilter(String filter, HttpServletRequest request, Class beanClass) {
if (filter != null && Page.class.isAssignableFrom(beanClass)) {
filter = "**,content[" + filter + "]";
}
return filter;
}
});

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.

How to display customized error response in REST API

My url is http://localhost:8090/employee/?emp_id=1551&name=
I am using Spring boot for designing REST application. I have used #RequestMapping and #RequestParam annotation for get resource. When I pass empty value to request parameter (for eg. name = ), I get below validation response(actual output section below).
However I wanted to override this output to display customized error response as below(expected section below).
How can I achieve this? How to avoid Spring's auto validation for input parameters in Get request?
Output
======
{
"timestamp": 1511144660708,
"status": 400,
"error": "Bad Request",
"message": "Required String parameter 'name' is not present",
"path": "/employee"
}
Expected
========
{
"errors":[
{
"id":"123144",
"detail": "invalid user input"
"status": "400"
}
]
}
Following sample code demonstrates how to customize error message for exception handling.
Create 2 POJOs for your customized response body.
Implement 1 method to catch the MissingServletRequestParameterException exception with #ExceptionHandler annotation for missing paramters.
Generate the response as you expected.
Class: ResponseProperty.java
public class ResponseProperty {
private int id;
private String detail;
private int status;
//getters and setters produced by IDE
}
Class: ResponsePOJO.java
public class ResponsePOJO {
List<ResponseProperty> errors;
public List<ResponseProperty> getErrors() {
return errors;
}
public void setErrors(List<ResponseProperty> errors) {
this.errors = errors;
}
}
Method: handleMethodArgumentTypeMismatch
#ExceptionHandler({ MissingServletRequestParameterException.class })
public ResponseEntity<Object> handleMethodArgumentTypeMismatch(MissingServletRequestParameterException ex) {
ResponseProperty property = new ResponseProperty();
property.setId(123144);
property.setDetail("invalid user input");
property.setStatus(400);
ResponsePOJO responsePOJO = new ResponsePOJO();
List<ResponseProperty> propertyList = new ArrayList<ResponseProperty>();
propertyList.add(property);
responsePOJO.setErrors(propertyList);
return new ResponseEntity<Object>(responsePOJO, HttpStatus.BAD_REQUEST);
}
If you visit the endpoint /employee without required parameter, then you are going to see the response as follows:
Http Response
{
"errors": [
{
"id": 123144,
"detail": "invalid user input",
"status": 400
}
]
}
Hope this helps you! :)
UPDATE
If you want to get the request ID from header named requestId for response, you can use WebRequest to get this information as follows:
#ExceptionHandler({ MissingServletRequestParameterException.class })
public ResponseEntity<Object> handleMethodArgumentTypeMismatch(MissingServletRequestParameterException ex,
WebRequest request) {
ResponseProperty property = new ResponseProperty();
property.setId(Integer.valueOf(request.getHeader("requestId")));
...
}

Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported for #RequestBody MultiValueMap

Based on the answer for problem with x-www-form-urlencoded with Spring #Controller
I have written the below #Controller method
#RequestMapping(value = "/{email}/authenticate", method = RequestMethod.POST
, produces = {"application/json", "application/xml"}
, consumes = {"application/x-www-form-urlencoded"}
)
public
#ResponseBody
Representation authenticate(#PathVariable("email") String anEmailAddress,
#RequestBody MultiValueMap paramMap)
throws Exception {
if(paramMap == null || paramMap.get("password") == null) {
throw new IllegalArgumentException("Password not provided");
}
}
the request to which fails with the below error
{
"timestamp": 1447911866786,
"status": 415,
"error": "Unsupported Media Type",
"exception": "org.springframework.web.HttpMediaTypeNotSupportedException",
"message": "Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported",
"path": "/users/usermail%40gmail.com/authenticate"
}
[PS: Jersey was far more friendly, but couldn't use it now given the practical restrictions here]
The problem is that when we use application/x-www-form-urlencoded, Spring doesn't understand it as a RequestBody. So, if we want to use this
we must remove the #RequestBody annotation.
Then try the following:
#RequestMapping(
path = "/{email}/authenticate",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = {
MediaType.APPLICATION_ATOM_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE
})
public #ResponseBody Representation authenticate(
#PathVariable("email") String anEmailAddress,
MultiValueMap paramMap) throws Exception {
if (paramMap == null &&
paramMap.get("password") == null) {
throw new IllegalArgumentException("Password not provided");
}
return null;
}
Note that removed the annotation #RequestBody
answer: Http Post request with content type application/x-www-form-urlencoded not working in Spring
It seems that now you can just mark the method parameter with #RequestParam and it will do the job for you.
#PostMapping( "some/request/path" )
public void someControllerMethod( #RequestParam Map<String, String> body ) {
//work with Map
}
Add a header to your request to set content type to application/json
curl -H 'Content-Type: application/json' -s -XPOST http://your.domain.com/ -d YOUR_JSON_BODY
this way spring knows how to parse the content.
In Spring 5
#PostMapping( "some/request/path" )
public void someControllerMethod( #RequestParam MultiValueMap body ) {
// import org.springframework.util.MultiValueMap;
String datax = (String) body .getFirst("datax");
}
#RequestBody MultiValueMap paramMap
in here Remove the #RequestBody Annotaion
#RequestMapping(value = "/signin",method = RequestMethod.POST)
public String createAccount(#RequestBody LogingData user){
logingService.save(user);
return "login";
}
#RequestMapping(value = "/signin",method = RequestMethod.POST)
public String createAccount( LogingData user){
logingService.save(user);
return "login";
}
like that
Simply removing #RequestBody annotation solves the problem (tested on Spring Boot 2):
#RestController
public class MyController {
#PostMapping
public void method(#Valid RequestDto dto) {
// method body ...
}
}
I met the same problem when I want to process my simple HTML form submission (without using thymeleaf or Spring's form tag) in Spring MVC.
The answer of Douglas Ribeiro will work very well. But just in case, for anyone, like me, who really want to use "#RequestBody" in Spring MVC.
Here is the cause of the problem:
Spring need to ① recognize the "Content-Type", and ② convert the
content to the parameter type we declared in the method's signature.
The 'application/x-www-form-urlencoded' is not supported, because, by
default, the Spring cannot find a proper HttpMessageConverter to do
the converting job, which is step ②.
Solution:
We manually add a proper HttpMessageConverter into the Spring's
configuration of our application.
Steps:
Choose the HttpMessageConverter's class we want to use. For
'application/x-www-form-urlencoded', we can choose
"org.springframework.http.converter.FormHttpMessageConverter".
Add the FormHttpMessageConverter object to Spring's configuration,
by calling the "public void
configureMessageConverters(List<HttpMessageConverter<?>>
converters)" method of the "WebMvcConfigurer" implementation class
in our application. Inside the method, we can add any
HttpMessageConverter object as needed, by using "converters.add()".
By the way, the reason why we can access the value by using "#RequestParam" is:
According to Servlet Specification (Section 3.1.1):
The following are the conditions that must be met before post form
data will be populated to the parameter set: The request is an HTTP
or HTTPS request. 2. The HTTP method is POST. 3. The content type is
application/x-www-form-urlencoded. 4. The servlet has made an initial
call of any of the getParameter family of methods on the request
object.
So, the value in request body will be populated to parameters. But in Spring, you can still access RequestBody, even you can use #RequstBody and #RequestParam at the same method's signature.
Like:
#RequestMapping(method = RequestMethod.POST, consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public String processForm(#RequestParam Map<String, String> inputValue, #RequestBody MultiValueMap<String, List<String>> formInfo) {
......
......
}
The inputValue and formInfo contains the same data, excpet for the type for "#RequestParam" is Map, while for "#RequestBody" is MultiValueMap.
I wrote about an alternative in this StackOverflow answer.
There I wrote step by step, explaining with code. The short way:
First: write an object
Second: create a converter to mapping the model extending the AbstractHttpMessageConverter
Third: tell to spring use this converter implementing a WebMvcConfigurer.class overriding the configureMessageConverters method
Fourth and final: using this implementation setting in the mapping inside your controller the consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE and #RequestBody in front of your object.
I'm using spring boot 2.
#PostMapping(path = "/my/endpoint", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE })
public ResponseEntity<Void> handleBrowserSubmissions(MyDTO dto) throws Exception {
...
}
That way works for me
You can try to turn support on in spring's converter
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// add converter suport Content-Type: 'application/x-www-form-urlencoded'
converters.stream()
.filter(AllEncompassingFormHttpMessageConverter.class::isInstance)
.map(AllEncompassingFormHttpMessageConverter.class::cast)
.findFirst()
.ifPresent(converter -> converter.addSupportedMediaTypes(MediaType.APPLICATION_FORM_URLENCODED_VALUE));
}
}
Just add an HTTP Header Manager if you are testing using JMeter :

Resources