Spring: Method Not Allowed on file uploading - spring

I'm writing REST controller which should allow users to upload their files.
I have a class
#RestController
#RequestMapping("/profiles/{userId}/files")
public class FileController {
#PostMapping(path = "upload")
public String uploadFile(#PathVariable("userId") long userId,
#RequestParam("file") MultipartFile file) throws IOException {
//some logic here
}
But I get
{
"timestamp": 1502301243676,
"status": 405,
"error": "Method Not Allowed",
"message": "Method Not Allowed",
"path": "/profiles/12351/files/upload"
}
Am I doing something wrong? Post request without #PathVariable works fine, but I need this one.

Thank you, guys. At least I found the reason.
It was my fault: I have many different controllers and one of them was
"/profiles/{userId}/{source}" with the GET method which was mapped on the same endpoint.
So this controller tried to handle my request, but couldn't.
I changed path to it it and my first one became work.
Thank you for your attentions and answers.

Use below code
#RestController
#RequestMapping("/profiles/{userId}/files")
public class FileController {
#RequestMapping(value = "upload", method = RequestMethod.POST )
public String uploadFile(#PathVariable("userId") long userId,
#RequestParam("file") MultipartFile file) throws IOException {
//some logic here
}

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 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 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.

Spring MVC #RestController and redirect

I have a REST endpoint implemented with Spring MVC #RestController. Sometime, depends on input parameters in my controller I need to send http redirect on client.
Is it possible with Spring MVC #RestController and if so, could you please show an example ?
Add an HttpServletResponse parameter to your Handler Method then call response.sendRedirect("some-url");
Something like:
#RestController
public class FooController {
#RequestMapping("/foo")
void handleFoo(HttpServletResponse response) throws IOException {
response.sendRedirect("some-url");
}
}
To avoid any direct dependency on HttpServletRequest or HttpServletResponse I suggest a "pure Spring" implementation returning a ResponseEntity like this:
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(newUrl));
return new ResponseEntity<>(headers, HttpStatus.MOVED_PERMANENTLY);
If your method always returns a redirect, use ResponseEntity<Void>, otherwise whatever is returned normally as generic type.
Came across this question and was surprised that no-one mentioned RedirectView. I have just tested it, and you can solve this in a clean 100% spring way with:
#RestController
public class FooController {
#RequestMapping("/foo")
public RedirectView handleFoo() {
return new RedirectView("some-url");
}
}
redirect means http code 302, which means Found in springMVC.
Here is an util method, which could be placed in some kind of BaseController:
protected ResponseEntity found(HttpServletResponse response, String url) throws IOException { // 302, found, redirect,
response.sendRedirect(url);
return null;
}
But sometimes might want to return http code 301 instead, which means moved permanently.
In that case, here is the util method:
protected ResponseEntity movedPermanently(HttpServletResponse response, String url) { // 301, moved permanently,
return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).header(HttpHeaders.LOCATION, url).build();
}
As the redirections are usually needed in a not-straightforward path, I think throwing an exception and handling it later is my favourite solution.
Using a ControllerAdvice
#ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {
NotLoggedInException.class
})
protected ResponseEntity<Object> handleNotLoggedIn(
final NotLoggedInException ex, final WebRequest request
) {
final String bodyOfResponse = ex.getMessage();
final HttpHeaders headers = new HttpHeaders();
headers.add("Location", ex.getRedirectUri());
return handleExceptionInternal(
ex, bodyOfResponse,
headers, HttpStatus.FOUND, request
);
}
}
The exception class in my case:
#Getter
public class NotLoggedInException extends RuntimeException {
private static final long serialVersionUID = -4900004519786666447L;
String redirectUri;
public NotLoggedInException(final String message, final String uri) {
super(message);
redirectUri = uri;
}
}
And I trigger it like this:
if (null == remoteUser)
throw new NotLoggedInException("please log in", LOGIN_URL);
if you #RestController returns an String you can use something like this
return "redirect:/other/controller/";
and this kind of redirect is only for GET request, if you want to use other type of request use HttpServletResponse

Spring Boot Rest Controller how to return different HTTP status codes?

I am using Spring Boot for a simple REST API and would like to return a correct HTTP statuscode if something fails.
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
#ResponseBody
#ResponseStatus( HttpStatus.OK )
public RestModel create(#RequestBody String data) {
// code ommitted..
// how do i return a correct status code if something fails?
}
Being new to Spring and Spring Boot, the basic question is how do i return different status codes when something is ok or fails?
There are several options you can use. Quite good way is to use exceptions and class for handling called #ControllerAdvice:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
Also you can pass HttpServletResponse to controller method and just set response code:
public RestModel create(#RequestBody String data, HttpServletResponse response) {
// response committed...
response.setStatus(HttpServletResponse.SC_ACCEPTED);
}
Please refer to the this great blog post for details: Exception Handling in Spring MVC
NOTE
In Spring MVC using #ResponseBody annotation is redundant - it's already included in #RestController annotation.
One of the way to do this is you can use ResponseEntity as a return object.
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
public ResponseEntity<?> create(#RequestBody String data) {
if(everything_fine) {
return new ResponseEntity<>(RestModel, HttpStatus.OK);
} else {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
A nice way is to use Spring's ResponseStatusException
Rather than returning a ResponseEntityor similar you simply throw the ResponseStatusException from the controller with an HttpStatus and cause, for example:
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cause description here");
This results in a response to the client containing the HTTP status:
{
"timestamp": "2020-07-09T04:43:04.695+0000",
"status": 400,
"error": "Bad Request",
"message": "Cause description here",
"path": "/test-api/v1/search"
}
Note: HttpStatus provides many different status codes for your convenience.
In case you want to return a custom defined status code, you can use the ResponseEntity as here:
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
public ResponseEntity<?> create(#RequestBody String data) {
int customHttpStatusValue = 499;
Foo foo = bar();
return ResponseEntity.status(customHttpStatusValue).body(foo);
}
The CustomHttpStatusValue could be any integer within or outside of standard HTTP Status Codes.
Try this code:
#RequestMapping(value = "/validate", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<ErrorBean> validateUser(#QueryParam("jsonInput") final String jsonInput) {
int numberHTTPDesired = 400;
ErrorBean responseBean = new ErrorBean();
responseBean.setError("ERROR");
responseBean.setMensaje("Error in validation!");
return new ResponseEntity<ErrorBean>(responseBean, HttpStatus.valueOf(numberHTTPDesired));
}
There are different ways to return status code,
1 : RestController class should extends BaseRest class, in BaseRest class we can handle exception and return expected error codes.
for example :
#RestController
#RequestMapping
class RestController extends BaseRest{
}
#ControllerAdvice
public class BaseRest {
#ExceptionHandler({Exception.class,...})
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorModel genericError(HttpServletRequest request,
HttpServletResponse response, Exception exception) {
ErrorModel error = new ErrorModel();
resource.addError("error code", exception.getLocalizedMessage());
return error;
}
I think the easiest way is to make return type of your method as
ResponseEntity<WHATEVER YOU WANT TO RETURN>
and for sending any status code, just add return statement as
return ResponseEntity.status(HTTP STATUS).build();
For example, if you want to return a list of books,
public ResponseEntity<List<books>> getBooks(){
List<books> list = this.bookService.getAllBooks();
if(list.size() <= 0)
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
else
return ResponseEntity.of(Optional.of(list));
}

Resources