Close

Spring MVC - Providing a Model attribute value to a handler method by using @ModelAttribute

[Last Updated: Mar 30, 2026]

In the last tutorial we saw the primary purpose of @ModelAttribute i.e. if annotated on method level, the Model object is populated with common attributes before a handler method is invoked. Here we are going to see one more use of this annotation.

Two uses of @ModelAttribute:

  • Method-Level: Populates the model before the request handler is invoked. (last tutorial).
  • Parameter-Level: Retrieves an object from the model to be used as a handler argument. (this tutorial).

Using @ModelAttribute with a handler method parameter

@ModelAttribute annotation on a handler method parameter indicates the parameter's value should be retrieved from the Model map.

There are two main steps to accomplish that.

First step (as we saw in the last tutorial) is to annotate a method with @ModelAttribute and return an object of any type from that method.:

@Controller
@RequestMapping("users")
public class UserController {
    @Autowired
    private UserService userService;

    @ModelAttribute("user")
    public User getUser (@PathVariable("userId") long userId) {
        return userService.getUserById(userId);
    } 
}


Second step is to create a handler method and add an extra parameter of the same type returned in the first step. Also annotate the parameter with @ModelAttribute indicating its name.

@Controller
@RequestMapping("users")
public class UserController {
    @Autowired
    private UserService userService;


    @RequestMapping("{userId}")
    public String handleRequestById (@ModelAttribute("user") User user, Model model) {
        model.addAttribute("msg", "user  : " + user);
        return user!=null && "admin".equals(user.getRole()) ? 
                                                  "admin-page" : "user-page";
    }

    @ModelAttribute("user")
    public User getUser (@PathVariable("userId") long userId) {
        return userService.getUserById(userId);
    }
}


How it works?

Before invoking any handler methods annotated with @RequestMapping, Spring calls all @ModelAttribute methods.

All return values of @ModelAttribute methods are populated in the Model object.

Spring then moves to next step and prepares to call the target handler method (the one which matches the request). On finding @ModelAttribute in the target handler method parameters, Spring tries to find the named object in the Model map. In our example it looks by the name 'user'.

On finding the matching named object, Spring populates the handler parameter with its value and calls the handler.

Also, if there's @SessionAttributes annotation on the controller class, then Spring first checks whether the named value for the handler's @ModelAttribute parameter exists in the HttpSession, if yes, then it will be used rather than invoking the related method with @ModelAttribute. See next tutorial for details.

If there's no match found then Spring attempts to instantiate the object using it's default constructor, then assign to the parameter and call the handler.



When to use @ModelAttribute as handler parameter?

The object returned by @ModelAttribute annotated method, will already be in the Model object, which is accessible in the view. So why we need to access that object in our handler method? The answer is whenever we want to conveniently apply some logic in the handler, based on that object. Like in our above example: based on User#role we are selecting different view name.


Complete Example

package com.logicbig.example;

public class User {
    private long id;
    private String name;
    private String role;
    .............
}
package com.logicbig.example;

import org.springframework.stereotype.Service;

@Service
public class UserService {
   final static User USER_ADMIN = new User(1, "John", "admin");
    final static User USER_DEV = new User(2, "Jim", "dev");

    public User getUserById(long id) {
        //simulating getting user from db
        if (id == 1) {
          return USER_ADMIN;
        } else {
         return USER_DEV;
        }
    }
}
package com.logicbig.example;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("users")
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("{userId}")
    public String handleRequestById(@ModelAttribute("user") User user,
                                    Model model) {
        model.addAttribute("msg", "user by id: " +user);
        return user != null && "admin".equals(user.getRole()) ?
                "admin-page" : "user-page";
    }

    @ModelAttribute("user")
    public User getUser(@PathVariable("userId") long userId) {
        return userService.getUserById(userId);
    }
}

src/main/webapp/WEB-INF/views/admin-page.jsp

<%@ page language="java"
    contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<html>
<body>
<h2>Admin Page</h2>
   <h3> Message :  <h3>
      <p>${msg}</p>

</body>
</html>

src/main/webapp/WEB-INF/views/user-page.jsp

<%@ page language="java"
    contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<html>
<body>
<h2>User Page</h2>
<h3> Message :  <h3>
   <p>${msg}</p>
</body>
</html>

Running Example

To try examples, run embedded Jetty (configured in pom.xml of example project below):

mvn jetty:run


$ curl -s http://localhost:8080/users/1

<html>
<body>
<h2>Admin Page</h2>
<h3> Message : <h3>
<p>user by id: User{id=1, name='John', role='admin'}</p>

</body>
</html>
$ curl -s http://localhost:8080/users/2

<html>
<body>
<h2>User Page</h2>
<h3> Message : <h3>
<p>user by id: User{id=2, name='Jim', role='dev'}</p>
</body>
</html>

Integration Test

package com.logicbig.example;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class UserControllerTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvcTester mockMvc;

    @BeforeEach
    public void setup() {
        // MockMvcTester can be created directly from the WebApplicationContext
        this.mockMvc = MockMvcTester.from(wac);
    }

    @Test
    public void testUserController() {
        // Test Admin User
        var adminResponse = this.mockMvc.get().uri("/users/1").exchange();

        assertThat(adminResponse).hasStatusOk();
        assertThat(adminResponse).hasViewName("admin-page");
        assertThat(adminResponse).model().containsEntry("user", UserService.USER_ADMIN);

        // Test Dev User
        var devResponse = this.mockMvc.get().uri("/users/2").exchange();

        assertThat(devResponse).hasStatusOk();
        assertThat(devResponse).hasViewName("user-page");
        assertThat(devResponse).model().containsKey("user");
        assertThat(devResponse).model().extractingByKey("user")
                                 .isEqualTo(UserService.USER_DEV);
    }
}
mvn clean test -Dtest="UserControllerTest"

Output

$ mvn clean test -Dtest="UserControllerTest"
[INFO] Scanning for projects...
[WARNING]
[WARNING] Some problems were encountered while building the effective model for com.logicbig.example:spring-model-attribute-on-handler:war:1.0-SNAPSHOT
[WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-war-plugin is missing. @ line 42, column 21
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING]
[INFO]
[INFO] -------< com.logicbig.example:spring-model-attribute-on-handler >-------
[INFO] Building spring-model-attribute-on-handler 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- clean:3.2.0:clean (default-clean) @ spring-model-attribute-on-handler ---
[INFO] Deleting D:\example-projects\spring-mvc\spring-model-attribute-on-handler\target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ spring-model-attribute-on-handler ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-model-attribute-on-handler\src\main\resources
[INFO]
[INFO] --- compiler:3.5.1:compile (default-compile) @ spring-model-attribute-on-handler ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 5 source files to D:\example-projects\spring-mvc\spring-model-attribute-on-handler\target\classes
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-model-attribute-on-handler ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-model-attribute-on-handler\src\test\resources
[INFO]
[INFO] --- compiler:3.5.1:testCompile (default-testCompile) @ spring-model-attribute-on-handler ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\example-projects\spring-mvc\spring-model-attribute-on-handler\target\test-classes
[INFO]
[INFO] --- surefire:3.2.5:test (default-test) @ spring-model-attribute-on-handler ---
[INFO] Using auto detected provider org.apache.maven.surefire.junit4.JUnit4Provider
[WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.logicbig.example.UserControllerTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.497 s -- in com.logicbig.example.UserControllerTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.660 s
[INFO] Finished at: 2026-03-30T20:15:22+08:00
[INFO] ------------------------------------------------------------------------

Example Project

Dependencies and Technologies Used:

  • spring-webmvc 7.0.6 (Spring Web MVC)
     Version Compatibility: 3.2.9.RELEASE - 7.0.6Version List
    ×

    Version compatibilities of spring-webmvc with this example:

      javax.servlet-api:3.x
    • 3.2.9.RELEASE
    • 3.2.10.RELEASE
    • 3.2.11.RELEASE
    • 3.2.12.RELEASE
    • 3.2.13.RELEASE
    • 3.2.14.RELEASE
    • 3.2.15.RELEASE
    • 3.2.16.RELEASE
    • 3.2.17.RELEASE
    • 3.2.18.RELEASE
    • 4.0.0.RELEASE
    • 4.0.1.RELEASE
    • 4.0.2.RELEASE
    • 4.0.3.RELEASE
    • 4.0.4.RELEASE
    • 4.0.5.RELEASE
    • 4.0.6.RELEASE
    • 4.0.7.RELEASE
    • 4.0.8.RELEASE
    • 4.0.9.RELEASE
    • 4.1.0.RELEASE
    • 4.1.1.RELEASE
    • 4.1.2.RELEASE
    • 4.1.3.RELEASE
    • 4.1.4.RELEASE
    • 4.1.5.RELEASE
    • 4.1.6.RELEASE
    • 4.1.7.RELEASE
    • 4.1.8.RELEASE
    • 4.1.9.RELEASE
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • javax.servlet-api:4.x
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • 5.3.32
    • 5.3.33
    • 5.3.34
    • 5.3.35
    • 5.3.36
    • 5.3.37
    • 5.3.38
    • 5.3.39
    • javax.* -> jakarta.*
      jakarta.servlet-api:6.x
      Java 17 min
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.0.16
    • 6.0.17
    • 6.0.18
    • 6.0.19
    • 6.0.20
    • 6.0.21
    • 6.0.22
    • 6.0.23
    • 6.1.0
    • 6.1.1
    • 6.1.2
    • 6.1.3
    • 6.1.4
    • 6.1.5
    • 6.1.6
    • 6.1.7
    • 6.1.8
    • 6.1.9
    • 6.1.10
    • 6.1.11
    • 6.1.12
    • 6.1.13
    • 6.1.14
    • 6.1.15
    • 6.1.16
    • 6.1.17
    • 6.1.18
    • 6.1.19
    • 6.1.20
    • 6.1.21
    • 6.2.0
    • 6.2.1
    • 6.2.2
    • 6.2.3
    • 6.2.4
    • 6.2.5
    • 6.2.6
    • 6.2.7
    • 6.2.8
    • 6.2.9
    • 6.2.10
    • 6.2.11
    • 6.2.12
    • 6.2.13
    • 6.2.14
    • 6.2.15
    • 6.2.16
    • 6.2.17
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3
    • 7.0.4
    • 7.0.5
    • 7.0.6

    Versions in green have been tested.

  • spring-test 7.0.6 (Spring TestContext Framework)
  • jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
  • junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
  • hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
  • assertj-core 3.26.3 (Rich and fluent assertions for testing in Java)
  • JDK 25
  • Maven 3.9.11

Spring MVC - @ModelAttribute handler method argument Select All Download
  • spring-model-attribute-on-handler
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • UserController.java
          • webapp
            • WEB-INF
              • views
        • test
          • java
            • com
              • logicbig
                • example

    See Also

    Join