@ModelAttribute at the Method Level – The impact of @ModelAttribute when paced over a method.

Share on facebook
Share on google
Share on twitter
Share on linkedin
Why should we annotate a method with @ModelAttribute? Learn Spring's @ModelAttribute annotation in depth.

1. Overview


A method with a @ModelAttribute on top of it executes first, followed by a handler method.

Using @ModelAttribute at the method level, We can set data in the Model before a handler method executes. In simple words, It helps us to prepopulate the Model object before a Request Mapping method invokes.

We can set all the common attributes inside a method by marking it with a @ModelAttribute So that those data will be available during each request.

By using @ModelAttribute at the method level, We can also set global data for our web-app.

A controller can have any number of methods marked with a @ModelAttribute annotation.

In this article, I will cover the following questions on @ModelAttribute.

  1. How to use @ModelAttribute?
  2. Why should we use @ModelAttribute?
  3. When to use @ModelAttribute? (The Top 2 reasons)
  4. How to make @ModelAttribute data global?

2. Setting Up

Let’s create a sample MVC app that will show our Website Name and Website Category on a webpage.

To demonstrate, I am going to use a DTO called “WebsiteInfoDTO” to hold our website information.

public class WebsiteInfoDTO {

	private String websiteName;
	private String websiteCategory;
	
	public WebsiteInfoDTO() {
		System.out.println("WebsiteInfoDTO constructor called!");
	}

	//getters and setters 
	public String getWebsiteName() {
		return websiteName;
	}

	public void setWebsiteName(String websiteName) {
		this.websiteName = websiteName;
	}

	public String getWebsiteCategory() {
		return websiteCategory;
	}

	public void setWebsiteCategory(String websiteCategory) {
		this.websiteCategory = websiteCategory;
	}

}

As per requirement, we need to show the website name and category on a JSP page called “index.jsp”.

Let’s First Create a controller through which we can send the desired data to the JSP page.

@Controller
public class MyWebsiteController {

	@RequestMapping("/showInfo")
	public String showWebsiteInfo(Model model) {

		System.out.println("showWebsiteInfo method called..");

		// Setting the web site Information
		WebsiteInfoDTO websiteInfoDTO = new WebsiteInfoDTO();
		websiteInfoDTO.setWebsiteName("Selenium Express");
		websiteInfoDTO.setWebsiteCategory("Education");

		// adding the websiteInfoDTO to model
		model.addAttribute("websiteInfo", websiteInfoDTO);

		return "index";
	}
}

Now Let’s Develop the “Index.jsp” where we will fetch the Website name and website category from the model attribute.

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<html>
<body>

	Web site Name : ${websiteInfo.websiteName}
	<br />
	Web site Category : ${websiteInfo.websiteCategory}

</body>
</html>

Now, When we hit “http://localhost:8080/model-attribute-demo/showInfo“, we can see the website name and category data on the “index.jsp” page.

If we look at our console right now, We can see the below logs as the “WebsiteInfoDTO constructor” and the “showWebsiteInfo()” got invoked.

blank

3. Bringing in @ModelAttribute

Let’s try and answer the following questions.
How to use @ModelAttribute?
Why should we use @ModelAttribute?

The Below handler method handles the “/showInfo” request.
We may have multiple @RequestMapping methods, where you will be needing the WebsiteInfoDTO’s data.
For example, we may need the WebsiteInfoDTO data inside “/abc“, “/xyz” request mapping methods too.

@RequestMapping("/showInfo")
	public String showWebsiteInfo(Model model) {

		System.out.println("showWebsiteInfo method called..");

		// Setting the web site Information
		WebsiteInfoDTO websiteInfoDTO = new WebsiteInfoDTO();
		websiteInfoDTO.setWebsiteName("Selenium Express");
		websiteInfoDTO.setWebsiteCategory("Education");

		// adding the websiteInfoDTO to model
		model.addAttribute("websiteInfo", websiteInfoDTO);

		return "index";
	}

So, If we need some default model data on each request, It is batter to move these highlighted codes to a new method and annotate that with @ModelAttribute.

@ModelAttribute("websiteInfo")
	public WebsiteInfoDTO getWebsiteInfoDTO() {

		System.out.println("@ModelAttribute : inside getWebsiteDTO method");

		// setting website information
		WebsiteInfoDTO websiteInfoDTO = new WebsiteInfoDTO();
		websiteInfoDTO.setWebsiteName("Seleniumexpress.com");
		websiteInfoDTO.setWebsiteCategory("Education");
		System.out.println("************");

		return websiteInfoDTO;
	}
  • When we annotate a method with @ModelAttribute, It invokes first before any handler/@RequestMapping methods.
  • Any data we set inside a method annotated with @ModelAttribute, Available to every request of a controller.

Let’s Prove it by updating our Controller code.

@Controller
public class MyWebsiteController {

	@ModelAttribute("websiteInfo")
	public WebsiteInfoDTO getWebsiteInfoDTO() {

		System.out.println("@ModelAttribute : inside getWebsiteDTO method");

		// setting website information
		WebsiteInfoDTO websiteInfoDTO = new WebsiteInfoDTO();
		websiteInfoDTO.setWebsiteName("Seleniumexpress.com");
		websiteInfoDTO.setWebsiteCategory("Education");
		System.out.println("************");

		return websiteInfoDTO;
	}

	@RequestMapping("/showInfo")
	public String showWebsiteInfo(Model model) {
		
        System.out.println("@RequestMapping: showWebsiteInfo method called..");

		// fetching the model attribute"websiteInfo" from the model

		WebsiteInfoDTO websiteInfoDTO = (WebsiteInfoDTO) model.getAttribute("websiteInfo");
		System.out.println(websiteInfoDTO.getWebsiteName());
		System.out.println(websiteInfoDTO.getWebsiteCategory());

		return "index";
	}
}

If we hit the “http://localhost:8080/model-attribute-demo/showInfo“, First the method with @ModelAttribute will be called, followed by the @Requestmapping.

Once the @ModelAttribute method got executed, the WebsiteInfoDTO data will be available to “/showInfo” handler method.

@ModelAttribute : In Action

If we analyse the above log,

When there is an incoming request(“/showInfo“), The method with @ModelAttribute places the available model data inside the Model object of the @RequestMapping. 

In short, Our Common data is now available to each request coming to the MyWebsiteController.

That’s why you can see the WebsiteInfoDTO related information in the log when the “/showInfo” invokes.

When the handler method “showWebsiteInfo()” invocation is complete, the model is attached to it, going to the view. Hense you can see your data on the webpage as well.

blank

4. Let’s Introduce a new handler method

For more clarity, Let me create another @RequestMapping method inside our MyWebsiteController.
But this time, let’s use @ModelAttribute inside the handler method parameter instead of a normal Model.

@RequestMapping("/showCompanyInfo")
	public String showCompanyInfo(@ModelAttribute("websiteInfo") WebsiteInfoDTO websiteInfoDTO) {
		
		System.out.println("@RequestMapping: showCompanyInfo method called..");

		// fetching the model attribute"websiteInfo" from the model
	
		System.out.println(websiteInfoDTO.getWebsiteName());
		System.out.println(websiteInfoDTO.getWebsiteCategory());

		return "index";
	}

When we use a @ModelAttribute(“websiteInfo”) inside the @RequestMapping method parameter, first, it looks for the websiteInfoDTO object inside the model object. If the object is not available, the @ModelAttribute inside the method argument creates a new object for the given instance.

In Our Case, We already have a method annotated with @ModelAttribute over it i.e getWebsiteInfoDTO().
So when a new request comes, the getWebsiteInfoDTO() method will execute first and, the “WebsiteInfoDTO” instance will available to the request(“/showCompanyInfo”).

Hence, when spring handles the “/showCompanyInfo” request, The “WebsiteInfoDTO” object will be retrieved from the model and pushed to the handler method parameter’s @ModelAtrribute.

So when we write this line,

showCompanyInfo(@ModelAttribute("websiteInfo") WebsiteInfoDTO websiteInfoDTO) {

No new WebsiteInfoDTO object will be created and, the parameter level @ModelAttribute will use the instance created by the getWebsiteInfoDTO().

now let’s update our controller with our new handler method called showCompanyInfo().

@Controller
public class MyWebsiteController {

	@ModelAttribute("websiteInfo")
	public WebsiteInfoDTO getWebsiteInfoDTO() {

		System.out.println("************");
		System.out.println("@ModelAttribute : inside getWebsiteDTO method");

		// setting website information
		WebsiteInfoDTO websiteInfoDTO = new WebsiteInfoDTO();
		websiteInfoDTO.setWebsiteName("Seleniumexpress.com");
		websiteInfoDTO.setWebsiteCategory("Education");
		System.out.println("************");

		return websiteInfoDTO;
	}

	@RequestMapping("/showInfo")
	public String showWebsiteInfo(Model model) {
		System.out.println("@RequestMapping: showWebsiteInfo method called..");

		// fetching the model attribute"websiteInfo") from the model

		WebsiteInfoDTO websiteInfoDTO = (WebsiteInfoDTO) model.getAttribute("websiteInfo");
		System.out.println(websiteInfoDTO.getWebsiteName());
		System.out.println(websiteInfoDTO.getWebsiteCategory());
		

		return "index";
	}
	
	@RequestMapping("/showCompanyInfo")
	public String showCompanyInfo(@ModelAttribute("websiteInfo") WebsiteInfoDTO websiteInfoDTO) {
		
		System.out.println("@RequestMapping: showCompanyInfo method called..");

		// fetching the model attribute"websiteInfo") from the model
	
		System.out.println(websiteInfoDTO.getWebsiteName());
		System.out.println(websiteInfoDTO.getWebsiteCategory());

		return "index";
	}
}

If we hit the “http://localhost:8080/model-attribute-demo/showInfo
followed by
http://localhost:8080/model-attribute-demo/showCompanyInfo
You can see our method with @ModelAttribute i.e getWebsiteInfoDTO(), invoked before each request.

Log:

@ModelAttribute : In Action

Index.jsp

blank

5. Access your @ModelAttribute data globally

If we want to access the @ModelAttribute method data globally, annotate your controller class with a @ControllerAdvice annotation. Make sure the controller class that you are marking with @ControllerAdvice should be the same class, which contains a method marked with @ModelAttribute. 

Now, In our example, Let’s annotate MyWebsiteController with a @ControllerAdvice so that we can access the model data inside any other controller’s request method.

Updated the MyWebsiteController with @ControllerAdvice annotation

@Controller
@ControllerAdvice
public class MyWebsiteController {

Let’s create a new controller called TestController.

@Controller
public class TestController {

	@RequestMapping("/test")
	public String test(@ModelAttribute("websiteInfo") WebsiteInfoDTO websiteInfoDTO) {
		
		System.out.println("@RequestMapping: Inside test method");
		System.out.println("Website name is : " + websiteInfoDTO.getWebsiteName());
		System.out.println("Website Cetagory is : " + websiteInfoDTO.getWebsiteCategory());
		
		return "index";
	}

}

Now, Let’s execute the test() by hitting
http://localhost:8080/model-attribute-demo/test

blank

Amazing !!
So, even though the @RequestMapping(“/test”) present inside a different controller, still, we can access the model data which we set inside MyWebsiteController.

The below screenshot proves that our model data are now global and, We can access it inside any controller.

Let’s analyse the Log :

blank

Therefore, Marking a controller class with a @ControllerAdvice converts the Model Attributes to Global Attributes.

If you want to restrict the scope for @ControllerAdvice, use the assignableTypes or basePackages inside @ControllerAdvice parameters.

By following the below code, we can restrict the ModelAttribute data between limited classes.

@Controller
@ControllerAdvice(assignableTypes = {TestController.class,TestController2.class})
public class MyWebsiteController {

By following the below code, we can restrict the ModelAttribute data between limited packages.

@Controller
@ControllerAdvice(basePackages = {"com.seleniumexpress.service","com.seleniumexpress.controllers"})
public class MyWebsiteController {

6. When to use @ModelAttribute?

  1. We can use the @ModelAttribute on method level to share default data, which we need during any handler method’s invocation. For Example, We can prepopulate a model object with specific attributes by loading it from the database. Note that the same model object can be further accessed by a handler method annotated with @ModelAttribute (inside parameter). Now, As the model object inside the handler method parameter is already carrying the data, We can apply validation to it by placing a @Valid annotation before the @ModelAttribute.
  2. A method marked with @ModelAttribute is generally used with the @SessionAttribute to instantiate a model attribute in advance. You can watch the below video from 53:14 to get more clarity on this statement.

If you are new to Spring’s @SessionAttributes annotation, Please consider watching the below video.

If you enjoyed reading this post and passionate about learning Spring MVC from scratch, Do check out my Spring MVC beginners and Spring MVC Intermediate courses for FREE.

7. Conclusion

In this tutorial, we have investigated the uses of @ModelAttribute on the method level. To practice this concept, you can pull the same code from the GitHub. You can alwayes watch the video attached with this blog to get more clarity.

Abhilash

Abhilash

Leave a Replay

About Me

blank

Hi, I am Abhilash. I am a Software Developer,  Youtuber, and content creator at Selenium Express.

Love Reading? Sign up for our Newsletter.

Recent Posts

Weekly Tutorial

Follow Us

Sign up for our Newsletter

Loved this article? Please do subscribe for notification and updates.