Spring controller gets invoked but returns 404 - java

I am writing a Spring Boot application. I have written a simple controller that gets invoked whenever the endpoint is hit, but it still returns status 404 and not the specified return value.
HelloController
#Controller
public class MessageRequestController {
#RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "application/json")
public String hello() {
System.out.println("Hit me!");
return "Hello, you!";
}
}
Now whenever I call localhost:8080/hello, I see the console log "Hit me!", but "Hello, you!" is never returned. Postman outputs:
{
"timestamp": 1516953147719,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/hello"
}
Application.java
#SpringBootApplication
#ComponentScan({"com.sergialmar.wschat"}) // this is the root package of everything
#EntityScan("com.sergialmar.wschat")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Change your method return a ResponseEntity<T>
#RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "application/json")
public ResponseEntity<String> hello() {
System.out.println("Hit me!");
return new ResponseEntity<String>("Hello, you!", HttpStatus.OK);
}
or change the controller to RestController
#RestController
public class MessageRequestController {...}
CURL
ubuntu:~$ curl -X GET localhost:8080/hello
Hello, you!

Short version:
Annotate your endpoint method with ResponseBody to bind the return value to the response body.
#Controller
public class MessageRequestController {
#RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "application/json")
#ResponseBody
public String hello() {
System.out.println("Hit me!");
return "Hello, you!";
}
}
You can instead annotate your class with RestController instead of Controller to apply ResponseBody to each method of the class.
#RestController
public class MessageRequestController {
#RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "application/json")
public String hello() {
System.out.println("Hit me!");
return "Hello, you!";
}
}
With #Controller, you use the default model-view from Spring Web MVC, and you're actually telling spring to render the view called Hello, you!.tml from your resources directory (src/main/resources/templates for a Spring Boot project, if I remember correctly).
You can read this article for more information about the Spring MVC REST Workflow.
Once you're more familiar with those concepts, you can even further customize your endpoint method using ResponseEntity.

As you see the "hit me", there's no mapping issue, but in your #RequestMapping annotation you specifie a produce type to "application/json" and you return a simple poor String not formatted and without any header('Content-Type: application/json').
Add the header and format the outpout.

When everything seems ok but receive 404, check this answer:
As you know:
In the Spring MVC you can return view as a String or ModelAndView object.
IMPORTANT NOTE:
In both cases you have to pay attention to relative/absolute path:
If you declare / in the beginning of the view name, you are using absolute path.
Namely it does not matter class level #RequestMapping and directly introduce itself as the final view name.
If you do not declare / in the beginning of the view name, you are using relative path (relative to the class path) and therefore it appends to the class level #RequestMapping to construct final view name.
So, you have to consider the above notes when use the Spring MVC.
Example:
1. create two HTML file test1.html and test2.html in the static folder of spring (boot) structure:
Please note that the class level #RequestMapping behaves as a folder path in the case of relative path.
--- resources
--- static
--- classLevelPath //behaves as a folder when we use relative path scenario in view names
--- test2.html //this will be used for relative path [case (2)]
--- test1.html //this will be used for absolute path [case (1)]
create a controller class like as the below. This example shows different cases with return String and ModelAndView in both relative and absolute path.
#Controller
#RequestMapping("/classLevelPath")
public class TestController {
//case(1)
#RequestMapping("/methodLevelAbsolutePath1")
public String absolutePath1(Model model){
//model.addAttribute();
//...
return "/test1.html";
}
//case(1)
#RequestMapping("/methodLevelAbsolutePath2")
public ModelAndView absolutePath2(Model model){
ModelAndView modelAndView = new ModelAndView("/test1.html");
//modelAndView.addObject()
//....
return modelAndView;
}
//case(2)
#RequestMapping("/methodLevelRelativePath1")
public String relativePath1(Model model){
//model.addAttribute();
//...
return "test2.html";
}
//case(2)
#RequestMapping("/methodLevelRelativePath2")
public ModelAndView relativePath2(Model model){
ModelAndView modelAndView = new ModelAndView("test2.html");
//modelAndView.addObject()
//....
return modelAndView;
}
}
Note:
You can specify the suffix of your view files by a ViewResolver (for example InternalResourceViewResolver or spring.mvc.view.suffix=.html in the appliction.properties file of Spring Boot and do not declare .html suffix in the above code.
Best Regard

Related

How to design API in Spring MVC?

I have a Spring MVC controller but I'm not sure that it is a good or bad design. As far as I know, api versioning is missing but apart from that I implemented Swagger for documentation and added SpringSecurity and tried to follow YARAS(Yet Another RESTful API Standard) to build it but I need another eye on that to comment it.
#Slf4j
#Controller
#RequestMapping
#RequiredArgsConstructor
public class XGameController implements GameController {
private final GameService gameService;
private final ObjectMapper mapper;
#RequestMapping(value = "/", method= RequestMethod.GET)
public String index() {
return "game";
}
#RequestMapping(value = "/login", method= RequestMethod.GET)
public String login() {
return "login";
}
#Secured("ROLE_USER")
#RequestMapping(value = "/games", method= RequestMethod.POST)
public String initializeGame(Model model) {
log.info("New XGame is initializing...");
Game game = new Game();
game = gameService.initializeGame(game.getId());
try {
model.addAttribute("game", mapper.writeValueAsString(game));
} catch (JsonProcessingException e) {
log.error(e.getMessage());
}
log.info("New XGame is initialized successfully!");
return "game";
}
#Secured("ROLE_USER")
#RequestMapping(value = "/games/{gameId}", method= RequestMethod.PUT)
public #ResponseBody Game play(#PathVariable("gameId") String gameId,
#RequestParam Integer pitNumber,
#RequestParam String action) {
log.info("Sowing stone is triggered...");
return gameService.executeGameRules(UUID.fromString(gameId), pitNumber);
}
#RequestMapping(value = "/403", method= RequestMethod.GET)
public String error403() {
return "/error/403";
}
}
My swagger snapshot;
I would make some changes.
In /games/{gameId} I would use PATCH instead of PUT. The reason is that PUT is intended to completely replace the resource (in your case, the Game). This does not seem to be what you are doing in this endpoint. PATCH is intended to partially update a resource, which seems much more suited to what you are doing here.
Still in /games/{gameId} I would use the request body to provide the needed data instead of query parameters. It simply doesn't seem right. Query parameters are way more suited to GET requests than to POST, PUT or PATCH.
I would rename /403 to something else that actually gives some context about what 403 is. Having said this, I would go with /error-pages/403. Additionally, I would also consider removing this endpoint from the swagger specification.
Other than this, it seems fine to me.
Some advices :
Use a path that represents the context or the idea of your controller and you can add the version
#RequestMapping("/V1/xgame")
Use specialized annotations such as : #GetMapping, #PostMapping etc...
For /403 use a meaning full name such as /errors
Use custom message that you will return to the users. For that you need a ControllerAdvice.
Google on patterns and best practices Rest API design
Read some books for better undertanding.
Firstly, instead of #RequestMapping use a specific Mapping(Get, Post,etc.) and the use of type of mapping is up to you which you find more particular to the cause of using it
if you are redirecting from a page to homepage try to use return "redirect:"/url"" instead of just returning HTML file directly.
Rename your method for error, RequestMapping value to some more reasonable name.
instead of using return "/error/403"
use return "redirect:/error/403"

How can I get my spring boot controller to read the body of my http request to an object if the http request has content type urlencoded?

I am new to spring and spring boot and have set up a simple controller that can read http requests to an object if there is a header set setting the content-type to application/json.
However, when I do not set the content type in a header, this does not work and I get the error: "Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported". I understand that at no point have I told the controller that I actually want the it to read the body as JSON rather than as urlencoded and I'm looking for a way of doing this.
I have experimented with the #RequestBody, #RequestParam and #ResponseBody annotations but so far have had no luck.
I have also looked at overriding the WebMvcConfigurer.configureContentNegotiation method by setting default and other media-types but am less clear on what I am doing here.
Here is my simple controller in its current form
public class GreetingController {
#RequestMapping(value = "/greeting",
method = RequestMethod.POST)
public Greeting postGreeting(#RequestBody Greeting body) {
return new Greeting(body.getId(),"hello " + body.getContent());
}
}
Here is the constructor for my greeting class for reference:
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
The body of my request is '{"id":10, "content": "world"}'
Ideally, I would like to find a way to be able to process an http request with no content type header set (so presumably defaulting to form-urlencoded) as JSON so that there is less to think about when setting up the post request and the controller is less brittle.
Try this:
#RestController
public class GreetingController {
#RequestMapping(value = "/greeting",
method = RequestMethod.POST)
public HttpEntity<String> postGreeting(#RequestBody Greeting body) {
//SETGREETING()
return new HttpEntity<String>("data has been saved");
}
}
And don't forget to accept application/json header.
Try below code
import org.springframework.web.bind.annotation.ModelAttribute;
public class GreetingController {
#RequestMapping(value = "/greeting",
method = RequestMethod.POST)
public Greeting postGreeting(#ModelAttribute Greeting body) {
return new Greeting(body.getId(),"hello " + body.getContent());
}
}

Multipart File upload using Springfox and Swagger-ui

I'm using Spring MVC as a rest controller and I've integrated Swagger-ui with my controller using Springfox. I'd like to have a method that is able to upload a file via the Swagger-ui interface. I only need two parameters, a long acting for an object id and the file to be uploaded.
#RestController
public class controller{
#RequestMapping(value="/upload", method=RequestMethod.POST)
public void uploadFile(#RequestParam Long id,
#RequestParam MultipartFile file){
//do some stuff
}
}
I've tried almost everything and I can't get a file upload button to appear. However, if I do:
#RestController
public class Controller{
#RequestMapping(value="/upload", method=RequestMethod.POST)
public void uploadFile(#RequestParam Long id,
#RequestPart File file){
//do some stuff
}
}
The file upload button appears, but it always throws http code 415 when trying to upload a file. Besides, I need the input to be a MultipartFile, not a regular File. Even if I use the #RequestPart annotation with Multipart File, the choose file to upload button does not appear. How can I get this to work???? Even:
#RestController
public class Controller{
#RequestMapping(value="/upload", method=RequestMethod.POST)
public void uploadFile(#RequestPart String metaData,
#RequestPart MultipartFile file){
//do some stuff
}
}
Won't work. If someone could give a walkthrough of how to get this button to appear for MultipartFile? I'd be forever grateful.
I think you are missing the consumes attribute of the #RequestMapping in your second snippet. See the following example
#RequestMapping(
path = "/upload",
method = RequestMethod.POST,
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<String> handleUpload(
#RequestPart("file") MultipartFile file,
#RequestParam("someId") Long someId,
#RequestParam("someOtherId") Long someOtherId) {
return new ResponseEntity<>();
}
Use
#RequestPart(required = true) MultipartFile file
And use the version number 2.1.0 or latest, there is a bug with previous versions.
https://github.com/springfox/springfox/issues/786
In my situation, there were two things I needed to do
My MultipartFile request param had to be named 'file', otherwise, swagger-ui wouldn't display the file upload input control
#RequestParam("file") MultipartFile file
I had to register the following bean
#Bean(name = "multipartResolver")
public CommonsMultipartResolver commonsMultipartResolver(){
return new CommonsMultipartResolver();
}
Try using #RequestPart for MultipartFile instead of #RequestParam
#RestController
public class controller {
#RequestMapping(value="/upload", method=RequestMethod.POST)
public void uploadFile(#RequestParam Long id,
#RequestPart MultipartFile file) {
//do some stuff
}
}
Two things...
Value of consumes should should be "multipart/form-data". consumes="multipart/form-data"
#RequestPart("file") #ApiParam(value="File", required=true) MultipartFile file

Spring MVC 404 page not found

I have an AuthenticationController working fine: all of its methods are running and I can see the logged output of methods. After successful Authentication I return a new modal like this:
modelAndView = new ModelAndView("redirect:/home/");
.....
return modelAndView;
I have another controller named HomePageController, but, after returning a model from Authentication, I am not able to get the code execution in any method of HomePageController.
What mappings do I need?
#Controller
#RequestMapping(value = "/home")
#SessionAttributes({"loginModel"})
public class HomePageController extends AbstractController {
Note: All methods in AuthenticationController are working fine..
web.xml file : http://snipt.org/vgEd7
mct-serverlet.xml file: http://snipt.org/vgEf2
replace trailing space
modelAndView = new ModelAndView("redirect:/home");
.....
return modelAndView;
That will look for /home/index.htm or something.
and in your HomepgageController, make sure there is some method, which returns view for /home url.
#Controller
#RequestMapping(value = "/home")
#SessionAttributes({"loginModel"})
public class HomePageController extends AbstractController {
public string handleHomePage(){
return "View Name";
}
}
Try this return statement : return new ModelAndView("redirect:home");
Avoid / in "redirect:/home/" after home action. It will look for index action automatically.

How to read flash attributes after redirection in Spring MVC 3.1?

I would like to know how to read a flash attributes after redirection in Spring MVC 3.1.
I have the following code:
#Controller
#RequestMapping("/foo")
public class FooController {
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(...) {
// I want to see my flash attributes here!
}
#RequestMapping(value = "/bar", method = RequestMethod.POST)
public ModelAndView handlePost(RedirectAttributes redirectAttrs) {
redirectAttrs.addFlashAttributes("some", "thing");
return new ModelAndView().setViewName("redirect:/foo/bar");
}
}
What I am missing?
Use Model, it should have flash attributes prepopulated:
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(Model model) {
String some = (String) model.asMap().get("some");
// do the job
}
or, alternatively, you can use RequestContextUtils#getInputFlashMap:
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(HttpServletRequest request) {
Map<String, ?> inputFlashMap = RequestContextUtils.getInputFlashMap(request);
if (inputFlashMap != null) {
String some = (String) inputFlashMap.get("some");
// do the job
}
}
P.S. You can do return return new ModelAndView("redirect:/foo/bar"); in handlePost.
EDIT:
JavaDoc says:
A RedirectAttributes model is empty when the method is called and is
never used unless the method returns a redirect view name or a
RedirectView.
It doesn't mention ModelAndView, so maybe change handlePost to return "redirect:/foo/bar" string or RedirectView:
#RequestMapping(value = "/bar", method = RequestMethod.POST)
public RedirectView handlePost(RedirectAttributes redirectAttrs) {
redirectAttrs.addFlashAttributes("some", "thing");
return new RedirectView("/foo/bar", true);
}
I use RedirectAttributes in my code with RedirectView and model.asMap() method and it works OK.
Try this:
#Controller
public class FooController
{
#RequestMapping(value = "/foo")
public String handleFoo(RedirectAttributes redirectAttrs)
{
redirectAttrs.addFlashAttribute("some", "thing");
return "redirect:/bar";
}
#RequestMapping(value = "/bar")
public void handleBar(#ModelAttribute("some") String some)
{
System.out.println("some=" + some);
}
}
works in Spring MVC 3.2.2
For all those like me who were having problems with seeing the POST url in the browser when a validation would fail.
The POST url is a private url that should not be exposed to users but it was automatically rendered when a validation failed. i.e. if a field was below a minimum length. I was using #Valid. I wanted the original GET url of the form to show at all times even when validation bounced back to the form, so I did the following:
if (validation.hasErrors()) {
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.story", validation);
redirectAttributes.addFlashAttribute("story", story);
return new ModelAndView("redirect:/january/2015");
where story is the form object representation, redirectAttributes are RedirectAttributes you put in the method signature and validation is the BindingResult. /january/2015 is the mapping to the GET controller where the form lives.
After this implementation, in the mapping for /january/2015, story comes in intact as follows:
Story story= (Story) model.asMap().get("story");
//story from the POST method
I had to augment my GET method and check if this was not null. If not null, then send this to the form else I would send a newly initialized Story type to the form as default behaviour before.
In this manner, I am able to return to the form with the bindingresults intact (errors show on form) but have my GET url in place of the post url.

Categories

Resources