Spring Boot
ModelAndView
ModelAndView is a Spring MVC holder that combines a logical view name and model data in a single return value. It is an alternative to returning a String view name and populating a separate Model parameter — useful when the view name is determined dynamically, when returning from exception handlers, or when working with legacy code.
ModelAndView Basics
ModelAndView bundles the logical view name and model attributes together. A handler method returns it instead of a String view name. Spring MVC unpacks it — passes the model to the ViewResolver and renders the named view. The result is identical to returning a String and populating a Model parameter separately.
Java
@Controller
@RequestMapping("/users")
public class UserController {
// ── String + Model (preferred for simple cases): ──────────────────
@GetMapping("/{id}")
public String findById(@PathVariable Long id, Model model) {
model.addAttribute("user", userService.findById(id));
return "users/detail";
}
// ── ModelAndView (equivalent result): ─────────────────────────────
@GetMapping("/{id}/mav")
public ModelAndView findByIdMav(@PathVariable Long id) {
ModelAndView mav = new ModelAndView("users/detail");
mav.addObject("user", userService.findById(id));
return mav;
}
// ── Constructor shorthand — view name + one attribute: ────────────
@GetMapping("/list")
public ModelAndView list() {
return new ModelAndView("users/list",
"users", userService.findAll());
// "users/list" = view name, "users" = attribute name, value = list
}
// ── Multiple attributes: ───────────────────────────────────────────
@GetMapping("/dashboard")
public ModelAndView dashboard() {
ModelAndView mav = new ModelAndView("users/dashboard");
mav.addObject("stats", userService.getStats());
mav.addObject("recentUsers", userService.findRecent(5));
mav.addObject("activeCount", userService.countActive());
return mav;
}
// ── Dynamic view name — chosen at runtime: ────────────────────────
@GetMapping("/{id}/view")
public ModelAndView dynamicView(
@PathVariable Long id,
@RequestParam(defaultValue = "detail") String layout) {
UserResponse user = userService.findById(id);
String viewName = switch (layout) {
case "compact" -> "users/compact";
case "print" -> "users/print";
default -> "users/detail";
};
return new ModelAndView(viewName, "user", user);
}
}ModelAndView with Redirect and Forward
ModelAndView supports the same redirect: and forward: view name prefixes as String returns. It also provides setViewName() for programmatic view switching and setStatus() for setting the HTTP status code.
Java
@Controller
@RequestMapping("/users")
public class UserController {
// ── Redirect: ─────────────────────────────────────────────────────
@PostMapping
public ModelAndView create(
@ModelAttribute @Valid CreateUserForm form,
BindingResult result) {
if (result.hasErrors()) {
ModelAndView mav = new ModelAndView("users/form");
mav.addObject("roles", User.Role.values());
return mav;
}
UserResponse created = userService.create(form);
ModelAndView mav = new ModelAndView("redirect:/users/" + created.id());
return mav;
}
// ── Setting HTTP status code: ──────────────────────────────────────
@GetMapping("/gone/{id}")
public ModelAndView gone(@PathVariable Long id) {
ModelAndView mav = new ModelAndView("users/gone");
mav.setStatus(HttpStatus.GONE); // 410 Gone
mav.addObject("id", id);
return mav;
}
// ── Returning null — signals no view should be rendered: ──────────
// Spring MVC assumes the handler wrote directly to HttpServletResponse:
@GetMapping("/raw")
public ModelAndView rawResponse(HttpServletResponse response)
throws IOException {
response.getWriter().write("raw text output");
return null; // or new ModelAndView() with no view set
}
}
// ── In @ExceptionHandler — ModelAndView is natural for error pages: ───
@ControllerAdvice // note: @ControllerAdvice, not @RestControllerAdvice
public class ErrorPageHandler {
@ExceptionHandler(UserNotFoundException.class)
public ModelAndView handleNotFound(UserNotFoundException ex) {
ModelAndView mav = new ModelAndView("error/404");
mav.addObject("message", ex.getMessage());
mav.setStatus(HttpStatus.NOT_FOUND);
return mav;
}
@ExceptionHandler(Exception.class)
public ModelAndView handleAll(Exception ex) {
ModelAndView mav = new ModelAndView("error/500");
mav.addObject("message", "An unexpected error occurred");
mav.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
return mav;
}
}ModelAndView vs String + Model
Both approaches produce identical results. The choice is stylistic, though each has cases where it is more natural.
Java
// ── Use String + Model when: ─────────────────────────────────────────
// - The view name is always the same for the method
// - You prefer parameter injection over object construction
// - The method is straightforward with no branching
@GetMapping
public String list(Model model) {
model.addAttribute("users", userService.findAll());
return "users/list";
}
// ── Use ModelAndView when: ────────────────────────────────────────────
// - The view name is determined dynamically (branching logic)
// - You need to set the HTTP status code
// - You are writing a @ControllerAdvice error handler
// - You prefer a single return value over separate model and view name
// - Legacy code or team conventions require it
@GetMapping("/{id}")
public ModelAndView detail(@PathVariable Long id,
@RequestParam boolean print) {
ModelAndView mav = new ModelAndView(print ? "users/print" : "users/detail");
mav.addObject("user", userService.findById(id));
if (print) mav.addObject("printDate", LocalDate.now());
return mav;
}
// ── They are fully interchangeable — no performance difference: ───────
// Spring MVC converts both into the same internal model + view name pair
// before passing to the ViewResolver.