In web development, data moves in two directions:
- Client → Server (request handling).
- Server → Client (forming responses).
We've already looked at the first direction, but the second one deserves our close attention. Let's figure out how the server can "answer" the client and why this matters for smooth interaction.
Response options
- HTML pages.
- The server returns HTML that the browser renders for the user.
- JSON/XML.
- Serialized data for clients (for example, in a REST API).
- Files or other resources.
- For example, images, PDFs, or ZIP files.
In Spring MVC we can craft responses flexibly thanks to a powerful set of annotations and tools.
Annotation @ResponseBody
If you come from the REST API world, the phrase "JSON responses" should be music to your ears. This annotation is exactly what sends data as JSON or XML from a controller back to the client.
Basically, @ResponseBody tells Spring: "Stop playing those pretty little stand-up shows with HTML pages. Just send the data to the client without the extra drama." That is, it bypasses view resolution and returns data directly in the HTTP response.
Example: Sending a JSON response
Here's the simplest example:
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/user")
public User getUser() {
return new User(1, "John Doe");
}
}
Here we use @RestController, which is basically a shortcut for @Controller + @ResponseBody. That means every method returns a JSON response. If you return an object, Spring will automatically convert it to JSON (serialize it) thanks to the built-in Jackson library.
How does it work? Spring can serialize objects to JSON thanks to the Jackson library. It's included out of the box, so you don't need to do anything special. If you want XML output, you'll need to add a bit of configuration.
Example: Using @ResponseBody without @RestController
If for some reason you're using a regular controller instead of @RestController, no worries! Just add @ResponseBody to the method.
@Controller
@RequestMapping("/api")
public class UserController {
@ResponseBody
@GetMapping("/user")
public User getUser() {
return new User(1, "Jane Doe");
}
}
This annotation tells Spring to return the data directly instead of looking for a view.
Can it return more than JSON?
Of course. If you want to return, say, a string or any other data type, @ResponseBody is happy to do that.
@Controller
@RequestMapping("/api")
public class ExampleController {
@ResponseBody
@GetMapping("/hello")
public String sayHello() {
return "Hello, Spring MVC!";
}
}
The client will just get the string: Hello, Spring MVC!.
Common mistakes when using @ResponseBody
- Error: "Cannot serialize object". This happens if the object can't be converted to JSON. For example, it has no public getters, or you forgot to include Jackson.
- Accidentally returning HTML. If you forget
@ResponseBodyor use@Controllerwithout it, Spring might try to find an HTML page with the same name as the return value (for example, "user" for thegetUsermethod).
Annotation @ModelAttribute
If @ResponseBody is your best friend for REST APIs, then @ModelAttribute is indispensable for classic MVC where you deal with HTML and server-side templates.
The @ModelAttribute annotation helps you pass data from the controller to the view. It loads an object into the model (yes, org.springframework.ui.Model!), so the template can use it later.
Example: passing an object to the view
Let's say we have an HTML page that shows user info. First we need to pass the data:
@Controller
@RequestMapping("/users")
public class UserController {
@GetMapping("/profile")
public String userProfile(Model model) {
User user = new User(1, "Jane Doe");
model.addAttribute("user", user);
return "userProfile";
}
}
Here we add the user object to the model, then return the view name (userProfile) that will render it. The view can be, for example, a Thymeleaf template.
Using @ModelAttribute
But why manually add to the model if you can use @ModelAttribute?
@Controller
@RequestMapping("/users")
public class UserController {
@ModelAttribute("user")
public User getUser() {
return new User(1, "Jane Doe");
}
@GetMapping("/profile")
public String userProfile() {
return "userProfile";
}
}
Spring will call the method annotated with @ModelAttribute automatically on any request to the controller, and the object will be added to the model under the given name (user). This reduces boilerplate and keeps your controller cleaner.
Example: working with forms
@ModelAttribute is also used to bind form data into Java objects.
@Controller
@RequestMapping("/users")
public class UserController {
@GetMapping("/register")
public String showRegistrationForm(Model model) {
model.addAttribute("user", new User());
return "register";
}
@PostMapping("/register")
public String processRegistration(@ModelAttribute("user") User user) {
System.out.println("Registered user: " + user);
return "registrationSuccess";
}
}
- GET request shows the registration form with an empty
Userobject. - POST request automatically binds the form data to the
Userobject.
Common mistakes when using @ModelAttribute
- Error: "NullPointerException". If you forgot to add the object to the model, the view won't be able to render the data.
- Binding errors. If you have a complex object and the form data can't be mapped, Spring will throw an error. Make sure all form fields match the object's fields.
When to use @ResponseBody and @ModelAttribute?
| Annotation | When to use |
|---|---|
@ResponseBody |
When you need to return JSON/XML or a string |
@ModelAttribute |
When you're working with HTML forms or templates |
Now you're armed with knowledge about forming responses in Spring MVC. Remember: @ResponseBody is the go-to for REST APIs, while @ModelAttribute will save you in the world of forms and server-rendered HTML.
GO TO FULL VERSION