java
40 lines · 8 steps
Building a Spring MVC form controller
A Spring MVC controller that renders an employee form, shares reference data, validates input, and redirects after saving.
Explained by
highlit
1@Controller
2@RequestMapping("/employees")
3public class EmployeeController {
4
5 private final DepartmentRepository departmentRepository;
6 private final EmployeeService employeeService;
7
8 public EmployeeController(DepartmentRepository departmentRepository, EmployeeService employeeService) {
9 this.departmentRepository = departmentRepository;
10 this.employeeService = employeeService;
11 }
12
13 @ModelAttribute("departments")
14 public List<Department> populateDepartments() {
15 return departmentRepository.findAllByActiveTrueOrderByName();
16 }
17
18 @ModelAttribute("employmentTypes")
19 public EmploymentType[] populateEmploymentTypes() {
20 return EmploymentType.values();
21 }
22
23 @GetMapping("/new")
24 public String newEmployeeForm(Model model) {
25 model.addAttribute("employee", new EmployeeForm());
26 return "employees/form";
27 }
28
29 @PostMapping
30 public String create(@Valid @ModelAttribute("employee") EmployeeForm form,
31 BindingResult bindingResult,
32 RedirectAttributes redirectAttributes) {
33 if (bindingResult.hasErrors()) {
34 return "employees/form";
35 }
36 Employee saved = employeeService.register(form);
37 redirectAttributes.addFlashAttribute("message", "Employee " + saved.getName() + " created");
38 return "redirect:/employees/" + saved.getId();
39 }
40}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1@ModelAttribute methods centralize reference data so every view under the controller has it without repeating lookups.
- 2Pairing @Valid with BindingResult lets you re-render the form on failure instead of throwing.
- 3The Post-Redirect-Get pattern with flash attributes prevents duplicate submissions and survives the redirect.
Related explainers
typescript
import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; import localeDe from '@angular/common/locales/de';
Locale-aware bootstrapping in Angular
i18n
localization
dependency-injection
Intermediate
8 steps
typescript
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as Joi from 'joi';
Validating env config at boot in NestJS
configuration
schema-validation
environment-variables
Intermediate
8 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; import { InjectRepository } from '@nestjs/typeorm';
A cache-aside country lookup in NestJS
cache-aside
dependency-injection
batch-lookup
Intermediate
8 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-spring-mvc-form-controller-explained-java-02c2/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.