Beginner-friendly interview and exam guide based on the uploaded Spring MVC training chapter. Learn the concepts first, then see complete code, diagrams, common traps, and scenario-based questions.
Imagine an Employee Registration page. The user enters a name, password, gender, country, community, age, joining date, and other information.
Spring MVC has to solve four connected problems:
The uploaded chapter's summary focuses on Spring MVC Forms, static/dynamic UI components, Model Attribute, standard/custom validation, and exception handling.
Forget Spring for a moment. A normal HTML form can look like this:
<form method="post" action="/register">
Name:
<input type="text" name="name">
Password:
<input type="password" name="password">
<button type="submit">
Register
</button>
</form>In Spring MVC, we usually have a Java object representing the form:
public class EmployeeBean {
private String name;
private String password;
private String gender;
private String country;
// getters and setters
}Spring Form tags connect the HTML controls to that Java object.
<%@ taglib
prefix="form"
uri="http://www.springframework.org/tags/form" %>Common tags from the chapter include:
| Tag | Purpose |
|---|---|
| <form:form> | Spring-aware form container |
| <form:input> | Text input |
| <form:password> | Password input |
| <form:radiobutton> | Single radio button |
| <form:radiobuttons> | Multiple/dynamic radio buttons |
| <form:select> | Select/dropdown |
| <form:option> | Individual option |
| <form:checkbox> | Single checkbox |
| <form:checkboxes> | Multiple checkboxes |
| <form:textarea> | Multi-line input |
| <form:hidden> | Hidden form field |
| <form:errors> | Display validation errors |
Suppose the controller sends an object under the name employeeBean.
<form:form
modelAttribute="employeeBean"
method="POST"
action="registration">Now:
<form:input path="name"/>means: connect this field to employeeBean.name.
<%@ taglib
prefix="form"
uri="http://www.springframework.org/tags/form" %>
<html>
<body>
<h2>Employee Registration</h2>
<!--
modelAttribute="employeeBean"
tells Spring that this form is bound
to the EmployeeBean object.
-->
<form:form
method="POST"
modelAttribute="employeeBean"
action="registration">
<label>Name:</label>
<!--
path="name"
connects this field with employeeBean.name
-->
<form:input path="name"/>
<br><br>
<label>Password:</label>
<!-- Connects with employeeBean.password -->
<form:password path="password"/>
<br><br>
<input type="submit" value="Register"/>
</form:form>
</body>
</html>@Controller
public class EmployeeController {
@RequestMapping(
value = "/loadEmployee",
method = RequestMethod.GET
)
public ModelAndView loadEmployeePage() {
/*
* Create the Java object that will be
* used by the Spring Form.
*/
EmployeeBean employeeBean = new EmployeeBean();
/*
* "Registration" = logical view name.
*
* "employeeBean" = name by which JSP
* will access the Java object.
*/
ModelAndView modelAndView =
new ModelAndView(
"Registration",
"employeeBean",
employeeBean
);
return modelAndView;
}
}Static means the values are written directly in the JSP.
<form:radiobutton
path="gender"
value="M"
label="Male"/>
<form:radiobutton
path="gender"
value="F"
label="Female"/>If Male is selected, employeeBean.getGender() can return M.
<form:select path="country">
<form:option
value=""
label="--Select--"/>
<form:option
value="1"
label="India"/>
<form:option
value="2"
label="USA"/>
<form:option
value="3"
label="UK"/>
</form:select><form:checkbox
path="mailingList"
label="Would you like to join our mailing list?"/>A single checkbox can represent a Boolean value.
<form:checkbox
path="community"
value="Spring"
label="Spring"/>
<form:checkbox
path="community"
value="Hibernate"
label="Hibernate"/>
<form:checkbox
path="community"
value="Struts"
label="Struts"/>The Java property can be an array/collection, for example:
private String[] community;<form:textarea path="aboutYou"/>Dynamic means the values are supplied at runtime, commonly through the backend.
@ModelAttribute("gender")
public Map<String, String> populateGenderRadio() {
/*
* Normally this could come from:
* Service → DAO → Database.
*/
return employeeService.getGender();
}Example returned map:
{
"M" : "Male",
"F" : "Female"
}JSP:
<form:radiobuttons
path="gender"
items="${gender}"/>@ModelAttribute("communityList")
public Map<String, String> populateCommunityCheckBoxes() {
return employeeService.getCommunities();
}<form:checkboxes
path="community"
items="${communityList}"/>@ModelAttribute("countryList")
public Map<String, String> populateCountries() {
return employeeService.getCountries();
}<form:select
path="country"
items="${countryList}"/>@ModelAttribute has two important uses in this chapter.
@ModelAttribute("countries")
public List<String> countries() {
return employeeService.getCountries();
}Think:
public ModelAndView register(
@ModelAttribute("employeeBean")
EmployeeBean employeeBean) {
// employeeBean contains bound form data.
return new ModelAndView("success");
}Think:
| Where? | Main idea |
|---|---|
| Above a method | Prepare Model data |
| On a method parameter | Bind request/form data to an object |
Think of the Spring Model as a temporary bag of data that the controller wants to make available to the view.
public String register(Model model) {
model.addAttribute(
"message",
"Registration successful"
);
return "success";
}public String register(ModelMap map) {
map.addAttribute(
"message",
"Registration successful"
);
return "success";
}request.setAttribute(
"message",
"Hello"
);The chapter's demo highlights an important lifecycle point: during controller execution, the submitted bean is available through Spring's Model/ModelMap, while request attributes are exposed to the view during the later rendering stage.
Validation prevents invalid user input from continuing into business logic or being stored incorrectly.
public class EmployeeBean {
@Size(min = 2, max = 10)
private String name;
@NotEmpty
private String password;
@NotEmpty
private String gender;
@NotNull
@Range(min = 10, max = 110)
private Integer age;
@NotNull
@Range(min = 1000, max = 800000)
private Integer salary;
@Future
@DateTimeFormat(pattern = "dd-MMM-yyyy")
private Date joiningDate;
@AssertTrue
private Boolean mailingList;
// getters and setters
}| Annotation | Beginner meaning |
|---|---|
| @NotNull | Value cannot be null |
| @NotEmpty | Value cannot be null/empty for supported types |
| @Size | Length/size must be within a range |
| @Range | Numeric value must be inside a range |
| @Future | Date/time must be in the future |
| @AssertTrue | Boolean value must be true |
| @DateTimeFormat | Controls date parsing/formatting |
@Controller
public class EmployeeController {
@RequestMapping(
value = "/registration",
method = RequestMethod.POST
)
public ModelAndView register(
/*
* @Valid tells Spring:
* run validation annotations
* defined on EmployeeBean.
*/
@ModelAttribute("employeeBean")
@Valid EmployeeBean employeeBean,
/*
* Stores binding and validation errors.
*
* IMPORTANT:
* keep it immediately after the
* validated object parameter.
*/
BindingResult result) {
ModelAndView modelAndView =
new ModelAndView();
if (result.hasErrors()) {
/*
* Validation failed.
* Stay on the form.
*/
modelAndView.setViewName(
"Registration"
);
} else {
/*
* Validation passed.
* Continue with business logic.
*/
modelAndView.setViewName(
"RegistrationSuccess"
);
modelAndView.addObject(
"message",
"Welcome " + employeeBean.getName()
);
}
return modelAndView;
}
}<form:input path="name"/>
<form:errors
path="name"
cssClass="error"/>To display all errors:
<form:errors path="*"/>The chapter uses a message properties file for validation messages.
Size.employeeBean.name=Employee name must be between {2} and {1} characters.
NotEmpty.employeeBean.password=Password is required.
NotNull.employeeBean.age=Age is required.
Range.employeeBean.age=Age must be between {2} and {1}.Generic messages can also be defined, depending on the message-resolution configuration.
NotNull=This is a required field
NotEmpty=This is a required field
Future=Date should be a future date<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property
name="basename"
value="classpath:com/accenture/ltt/resources/messages"/>
<property
name="defaultEncoding"
value="UTF-8"/>
</bean>Use custom validation when the business rule is specific to your application and cannot be expressed conveniently with the standard constraints.
@Documented
@Target({
ElementType.FIELD,
ElementType.METHOD
})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(
validatedBy = EmployeeNameValidator.class
)
public @interface EmployeeNameValidatorVal {
String message()
default "{EmployeeNameValidatorVal}";
Class<?>[] groups()
default {};
Class<? extends Payload>[] payload()
default {};
}public class EmployeeNameValidator
implements ConstraintValidator<
EmployeeNameValidatorVal,
String> {
@Override
public void initialize(
EmployeeNameValidatorVal annotation) {
// Initialization logic can go here.
}
@Override
public boolean isValid(
String employeeName,
ConstraintValidatorContext context) {
if (employeeName == null) {
return false;
}
if (employeeName.length() < 3) {
return false;
}
return true;
}
}The important line is:
@Constraint(
validatedBy = EmployeeNameValidator.class
)public class EmployeeBean {
@EmployeeNameValidatorVal
private String name;
}| Type | Example |
|---|---|
| Field validation | Age must be >= 18 |
| Cross-field validation | Password must equal confirmPassword |
| Cross-field business rule | Country + age combination must be valid |
The chapter also references a Spring MVC @InitBinder approach using the Spring Validator interface.
@InitBinder
protected void initBinder(
WebDataBinder binder) {
/*
* Register the custom Spring MVC validator.
*/
binder.addValidators(
new EmployeeValidator()
);
}public class EmployeeValidator
implements Validator {
@Override
public boolean supports(
Class<?> clazz) {
return EmployeeBean.class
.isAssignableFrom(clazz);
}
@Override
public void validate(
Object target,
Errors errors) {
EmployeeBean employee =
(EmployeeBean) target;
if (employee.getName() == null ||
employee.getName().length() < 3) {
errors.rejectValue(
"name",
"employee.name.invalid"
);
}
}
}Suppose validation succeeds but business logic discovers an invalid country/age combination and throws a custom exception.
Without handling, the user may see an ugly HTTP 500 response. The chapter demonstrates this problem before introducing exception handlers.
@ExceptionHandler(
value =
InvalidCountryNameAndAgeCombinationException.class
)
public ModelAndView
handleInvalidCountryAgeException(
InvalidCountryNameAndAgeCombinationException exception) {
ModelAndView modelAndView =
new ModelAndView();
/*
* Navigate to friendly error page.
*/
modelAndView.setViewName(
"ExceptionHandlerPage"
);
/*
* Put useful information into the model.
*/
modelAndView.addObject(
"message",
exception.getMessage()
);
modelAndView.addObject(
"exception",
exception
);
return modelAndView;
}@ExceptionHandler(
value = Exception.class
)
public ModelAndView
handleAllExceptions(
Exception exception) {
ModelAndView modelAndView =
new ModelAndView();
modelAndView.setViewName(
"GeneralizedExceptionHandlerPage"
);
modelAndView.addObject(
"message",
exception.getMessage()
);
modelAndView.addObject(
"exception",
exception
);
return modelAndView;
}Imagine you have EmployeeController, OrderController, PaymentController, and CustomerController. Copying the same exception handlers into every controller creates duplicated code.
@ControllerAdvice gives you a centralized place for controller-wide/global exception handling.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(
InvalidCountryNameAndAgeCombinationException.class
)
public ModelAndView
handleCountryAgeException(
InvalidCountryNameAndAgeCombinationException ex) {
ModelAndView modelAndView =
new ModelAndView(
"ExceptionHandlerPage"
);
modelAndView.addObject(
"message",
ex.getMessage()
);
return modelAndView;
}
@ExceptionHandler(Exception.class)
public ModelAndView
handleGeneralException(
Exception ex) {
ModelAndView modelAndView =
new ModelAndView(
"GeneralizedExceptionHandlerPage"
);
modelAndView.addObject(
"message",
"Something went wrong."
);
return modelAndView;
}
}| Concept | Think of it as |
|---|---|
| @ExceptionHandler | The method that handles an exception |
| @ControllerAdvice | A centralized place to share controller-related handling |
This is the diagram to memorize before the exam/interview.
Comprehensive, deep-dive interview preparation covering Spring Form taglib, two-way data binding, @ModelAttribute lifecycles, Bean Validation (JSR-380), BindingResult placement rules, custom validators, and @ControllerAdvice exception architecture.
Standard HTML forms only submit flat request parameters and cannot automatically pre-populate form fields when editing or redisplaying invalid inputs.
The Spring Form Taglib (<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>) provides two-way data binding:
modelAttribute and populates input values, selects, and checkboxes.BindingResult to highlight invalid fields via <form:errors>.| Spring Form Tag | Equivalent HTML | Key Attributes | Description & Usage |
|---|---|---|---|
<form:form> |
<form> |
modelAttribute, action, method |
Root container that binds the form to a model attribute bean. |
<form:input> |
<input type="text"> |
path |
Binds to a bean property for text input and auto-populates value. |
<form:password> |
<input type="password"> |
path, showPassword |
Password field (defaults to masking and clears value on validation failure). |
<form:select> |
<select><option> |
path, items, itemValue, itemLabel |
Renders dynamic dropdown menus populated from a List/Map in the Model. |
<form:checkboxes> |
Multiple <input type="checkbox"> |
path, items |
Binds multi-select checkboxes directly to a List<String> or String[]. |
<form:radiobuttons> |
Multiple <input type="radio"> |
path, items |
Renders a set of mutually exclusive radio options bound to a single property. |
<form:errors> |
<span class="error"> |
path, cssClass |
Displays field validation error messages (path="name") or all errors (path="*"). |
name="user_name". In Spring Form tags, you MUST use path="userName", which directly matches the JavaBean getter/setter property name. If the case or spelling does not match, Spring throws a NotReadablePropertyException.
One of the most frequently asked interview questions is the dual purpose of @ModelAttribute:
Purpose: Prepares reference data (like country lists, department dropdowns, roles) for the Model before any request handler method is invoked.
// Executed before every @RequestMapping in this controller
@ModelAttribute("countryList")
public List<String> populateCountries() {
return countryService.getAllCountryNames();
}
In JSP: <form:select path="country" items="${countryList}" />
Purpose: Binds submitted HTTP request parameters to a command object JavaBean, exposes it to the Model, and passes it as a method parameter.
@PostMapping("/register")
public String processRegistration(
@Valid @ModelAttribute("user") UserDTO userDTO,
BindingResult result,
Model model) {
if (result.hasErrors()) {
return "registerForm"; // Redisplay form with errors
}
userService.save(userDTO);
return "success";
}
| Mechanism | Type / Interface | Coupling Level | Lifecycle & Responsibility |
|---|---|---|---|
Model / ModelMap |
Spring Interface | Zero Servlet Coupling (Highly testable) | Carries data attributes for the view during controller processing. Exposes data to request scope only during view rendering. |
ModelAndView |
Spring Holder Object | Zero Servlet Coupling | Holds both the logical view name (e.g. "userProfile") and model data in a single return object. |
HttpServletRequest |
Servlet API | Tightly Coupled to Servlet Container | Low-level servlet container object. Testing requires mock request objects (e.g. MockHttpServletRequest). |
RedirectAttributes |
Spring Interface | Zero Servlet Coupling | Specialized for Post/Redirect/Get (PRG). Provides Flash Attributes stored temporarily in session across redirects. |
BindingResult MUST IMMEDIATELY FOLLOW the object annotated with @Valid / @ModelAttribute in the method signature.
@PostMapping("/save")
public String saveUser(
@Valid @ModelAttribute("user") UserDTO user,
Model model, // <-- WRONG! Model placed in between
BindingResult result) { // <-- Throws error or fails to bind
...
}
@PostMapping("/save")
public String saveUser(
@Valid @ModelAttribute("user") UserDTO user,
BindingResult result, // <-- CORRECT! Immediately after @Valid
Model model) {
if (result.hasErrors()) {
return "userForm";
}
return "userSuccess";
}
| Annotation | Applies To | Validation Rule | Common Trap |
|---|---|---|---|
@NotNull |
Any Object | Value must not be null |
Does NOT check empty strings ("" passes!). |
@NotEmpty |
CharSequence, Collection, Map, Array | Must not be null and size/length > 0 |
Strings with only whitespace (" ") pass. |
@NotBlank |
CharSequence (Strings) | Must not be null and contains at least 1 non-whitespace char | Best choice for form text fields (e.g. name, username). |
@Size(min=2, max=30) |
Strings, Collections, Arrays | Length or element count must be within range | If field is null, validation passes (pair with @NotNull). |
@Min(18) / @Max(100) |
Numeric types, BigDecimal | Value must be >= min or <= max | Used for age, salary, quantity constraints. |
@Pattern(regexp="...") |
String | Must match the specified regular expression | Used for phone numbers, zip codes, alphanumeric codes. |
@Email |
String | Must be a valid RFC-compliant email address | Accepts null; combine with @NotBlank. |
Best practice for reusable, declarative annotation-based validation across DTOs.
// 1. Custom Annotation
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CourseCodeValidator.class)
public @interface CourseCode {
String value() default "SP";
String message() default "must start with SP";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// 2. ConstraintValidator Implementation
public class CourseCodeValidator
implements ConstraintValidator<CourseCode, String> {
private String prefix;
@Override
public void initialize(CourseCode code) {
this.prefix = code.value();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) return true; // Let @NotNull handle nulls
return value.startsWith(prefix);
}
}
Best for cross-field validation (e.g. matching password & confirmPassword).
@Component
public class UserFormValidator implements org.springframework.validation.Validator {
@Override
public boolean supports(Class<?> clazz) {
return UserDTO.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
UserDTO user = (UserDTO) target;
// Cross-field comparison
if (!user.getPassword().equals(user.getConfirmPassword())) {
errors.rejectValue("confirmPassword", "password.mismatch", "Passwords do not match");
}
}
}
Spring provides three levels of exception handling, evaluated in priority order:
Scope: Single Controller class.
Usage: Handles exceptions thrown only within that specific controller.
@ExceptionHandler(UserNotFoundException.class)
public ModelAndView handleUserNotFound(UserNotFoundException ex) {
ModelAndView mv = new ModelAndView("error/userError");
mv.addObject("errorMsg", ex.getMessage());
return mv;
}
Scope: Application-wide (All controllers).
Usage: Centralized cross-cutting exception handling, model attributes, and init binders.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(SQLException.class)
public String handleDatabaseError(Model model) {
model.addAttribute("msg", "Database error occurred");
return "error/database";
}
@ExceptionHandler(Exception.class)
public String handleGenericError(Exception ex, Model model) {
model.addAttribute("msg", "An unexpected error occurred");
return "error/general";
}
}
Scope: Low-level Spring infrastructure.
Example: SimpleMappingExceptionResolver mapping exception class names directly to view names in XML.
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">error/general</prop>
</props>
</property>
</bean>
modelAttribute lacks a getter or setter matching the exact property name fullName (e.g. named getFullname() instead of getFullName()).IllegalStateException: Neither BindingResult nor plain target object for bean name 'employee' available as request attribute. You must populate an empty bean in the GET controller.@ModelAttribute("departments") method in the controller, then bind it in JSP via <form:select path="departmentId" items="${departments}" itemValue="id" itemLabel="name"/>.""), which is not null. You must use @NotBlank (or @NotEmpty) to reject empty/whitespace strings.@Valid or @Validated annotation.if (bindingResult.hasErrors()) { return "formView"; }. Validation populates errors into BindingResult, but it is the developer's responsibility to inspect it.BindingResult to the immediately preceding target object. Placing other parameters in between breaks this linkage.<form:errors path="*"/> for the complete summary at the top, and <form:errors path="email" cssClass="error"/> beside individual fields.messages.properties file with keys like NotBlank.user.username=Username is required and configure a ResourceBundleMessageSource bean.Validator implementation.@ControllerAdvice returns view names (JSP/Thymeleaf) or Model objects. @RestControllerAdvice combines @ControllerAdvice and @ResponseBody, serializing error responses directly to JSON/XML.<form:checkbox> automatically emits a hidden input prefixed with an underscore (_agree). If the checkbox is unchecked, Spring detects the underscore field and sets the boolean property to false.return "redirect:/success.html"). Spring supports RedirectAttributes.addFlashAttribute() to pass data across the redirect.Model attributes are request-scoped and automatically cleared after rendering, avoiding memory bloat and session concurrency bugs.