Spring MVC Controllers
Introduction
Spring MVC Controllers are the heart of Spring's web framework. They act as the traffic director in the Model-View-Controller (MVC) pattern, handling HTTP requests, processing them, and determining what response to send back. If you're building a web application with Spring, understanding controllers is essential.
In this tutorial, we'll explore:
- What Spring MVC Controllers are and why they're important
- How to create and configure basic controllers
- Different ways to handle web requests
- Working with request parameters, path variables, and request bodies
- Returning different types of responses
- Best practices and common patterns
What is a Spring MVC Controller?
A controller in Spring MVC is a Java class that handles web requests. It's annotated with @Controller or @RestController and contains methods (called handler methods) that process specific requests based on URL patterns, HTTP methods, and other criteria.
Key Characteristics of Controllers
- They receive and process HTTP requests
- They interact with service layers and models
- They determine what view should render the response (or return data directly)
- They handle exceptions that occur during request processing
Creating Your First Controller
Let's create a simple controller that returns a greeting message:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
    
    @GetMapping("/hello")
    @ResponseBody
    public String hello() {
        return "Hello, Spring MVC!";
    }
}
Breaking down this code:
- @Controller: This annotation marks the class as a web controller, capable of handling requests
- @GetMapping("/hello"): Maps HTTP GET requests to- /helloURL to this method
- @ResponseBody: Indicates that the return value should be bound to the web response body
- The method returns a simple string that will be displayed in the browser
When you run your Spring application and navigate to http://localhost:8080/hello, you'll see "Hello, Spring MVC!" displayed in your browser.
@Controller vs @RestController
Spring provides two primary annotations for creating controllers:
@Controller
The traditional annotation for Spring MVC controllers. When used alone, it expects methods to return view names, which Spring resolves to actual view templates (like JSP, Thymeleaf, etc.).
@Controller
public class ViewController {
    
    @GetMapping("/greet")
    public String greet(Model model) {
        model.addAttribute("message", "Hello from Spring MVC");
        return "greeting"; // Returns greeting.html template
    }
}
@RestController
This is a specialized version of the controller that combines @Controller and @ResponseBody. Every method in a @RestController automatically returns data directly in the response body, typically as JSON.
@RestController
public class ApiController {
    
    @GetMapping("/api/greeting")
    public Greeting getGreeting() {
        return new Greeting("Hello from the REST API!");
    }
}
Request Mapping Methods
Spring provides various annotations to map HTTP requests to controller methods:
| Annotation | HTTP Method | 
|---|---|
| @GetMapping | GET | 
| @PostMapping | POST | 
| @PutMapping | PUT | 
| @DeleteMapping | DELETE | 
| @PatchMapping | PATCH | 
These are specialized shortcuts for the more general @RequestMapping annotation.
Example using @RequestMapping:
@Controller
public class RequestMappingController {
    
    @RequestMapping(value = "/example", method = RequestMethod.GET)
    @ResponseBody
    public String exampleGet() {
        return "This handles GET requests to /example";
    }
    
    @RequestMapping(value = "/example", method = RequestMethod.POST)
    @ResponseBody
    public String examplePost() {
        return "This handles POST requests to /example";
    }
}
Handling Request Parameters
Spring MVC makes it easy to access request parameters:
Using @RequestParam
@GetMapping("/search")
@ResponseBody
public String search(@RequestParam String query, @RequestParam(defaultValue = "1") int page) {
    return "Searching for: " + query + " on page " + page;
}
If you navigate to /search?query=spring&page=2, the output would be:
Searching for: spring on page 2
For optional parameters, you can use:
@GetMapping("/profile")
@ResponseBody
public String getProfile(
    @RequestParam String username,
    @RequestParam(required = false) String section
) {
    if (section != null) {
        return "Profile for: " + username + ", Section: " + section;
    }
    return "Full profile for: " + username;
}
Path Variables
For RESTful URLs, path variables are often preferred:
@GetMapping("/users/{id}")
@ResponseBody
public String getUserById(@PathVariable Long id) {
    return "Fetching user with ID: " + id;
}
If you navigate to /users/42, the output would be:
Fetching user with ID: 42
You can have multiple path variables:
@GetMapping("/users/{userId}/posts/{postId}")
@ResponseBody
public String getUserPost(
    @PathVariable Long userId,
    @PathVariable Long postId
) {
    return "Fetching post " + postId + " for user " + userId;
}
Handling Form Submissions
Controllers can easily handle HTML form submissions:
@Controller
public class FormController {
    
    // Display the form
    @GetMapping("/register")
    public String showForm(Model model) {
        model.addAttribute("user", new User());
        return "registerForm";
    }
    
    // Process the form submission
    @PostMapping("/register")
    public String processRegistration(@ModelAttribute User user, Model model) {
        // Process user registration
        model.addAttribute("message", "Registration successful for " + user.getUsername());
        return "registrationResult";
    }
}
Working with JSON Requests and Responses
For RESTful APIs, you'll often work with JSON:
@RestController
public class ProductController {
    
    private final ProductService productService;
    
    public ProductController(ProductService productService) {
        this.productService = productService;
    }
    
    @PostMapping("/products")
    public Product createProduct(@RequestBody Product product) {
        return productService.save(product);
    }
    
    @GetMapping("/products")
    public List<Product> getAllProducts() {
        return productService.findAll();
    }
    
    @GetMapping("/products/{id}")
    public ResponseEntity<Product> getProductById(@PathVariable Long id) {
        Product product = productService.findById(id);
        if (product != null) {
            return ResponseEntity.ok(product);
        } else {
            return ResponseEntity.notFound().build();
        }
    }
}
Real-world Application: Task Management API
Let's create a more complete example of a task management API:
@RestController
@RequestMapping("/api/tasks")
public class TaskController {
    
    private final TaskService taskService;
    
    public TaskController(TaskService taskService) {
        this.taskService = taskService;
    }
    
    @GetMapping
    public List<Task> getAllTasks() {
        return taskService.findAll();
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<Task> getTaskById(@PathVariable Long id) {
        return taskService.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }
    
    @PostMapping
    public ResponseEntity<Task> createTask(@Valid @RequestBody Task task) {
        Task savedTask = taskService.save(task);
        URI location = ServletUriComponentsBuilder
            .fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(savedTask.getId())
            .toUri();
        return ResponseEntity.created(location).body(savedTask);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<Task> updateTask(
            @PathVariable Long id, 
            @Valid @RequestBody Task task) {
        if (!taskService.existsById(id)) {
            return ResponseEntity.notFound().build();
        }
        task.setId(id);
        Task updatedTask = taskService.save(task);
        return ResponseEntity.ok(updatedTask);
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteTask(@PathVariable Long id) {
        if (!taskService.existsById(id)) {
            return ResponseEntity.notFound().build();
        }
        taskService.deleteById(id);
        return ResponseEntity.noContent().build();
    }
    
    @GetMapping("/status/{status}")
    public List<Task> getTasksByStatus(@PathVariable String status) {
        return taskService.findByStatus(status);
    }
}
Exception Handling in Controllers
Spring provides several ways to handle exceptions in controllers:
Using @ExceptionHandler
@RestController
public class ProductController {
    
    // Controller methods...
    
    @ExceptionHandler(ProductNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleProductNotFound(
            ProductNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(
            "PRODUCT_NOT_FOUND", 
            ex.getMessage()
        );
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }
}
Using a Global Exception Handler
@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFound(
            ResourceNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(
            "RESOURCE_NOT_FOUND", 
            ex.getMessage()
        );
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }
    
    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<ErrorResponse> handleValidation(
            ValidationException ex) {
        ErrorResponse error = new ErrorResponse(
            "VALIDATION_ERROR", 
            ex.getMessage()
        );
        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
    }
}
Best Practices for Spring MVC Controllers
- 
Keep controllers focused - Controllers should handle HTTP requests and delegate business logic to service classes. 
- 
Use proper HTTP status codes - Return appropriate status codes (200 for success, 404 for not found, etc.). 
- 
Validate input - Use validation annotations like @Validto validate request data.
- 
Handle exceptions gracefully - Implement exception handling to provide meaningful error messages. 
- 
Use consistent URL patterns - Follow RESTful conventions for your endpoints. 
- 
Document your API - Use Swagger/OpenAPI to document your controller endpoints. 
- 
Test your controllers - Write unit and integration tests for your controllers. 
Summary
Spring MVC Controllers are the entry point for all web requests in a Spring application. They provide a powerful and flexible way to handle HTTP requests, process data, and return responses. We've covered:
- Creating basic controllers with @Controllerand@RestController
- Handling different HTTP methods with mapping annotations
- Working with request parameters and path variables
- Processing form submissions
- Working with JSON for RESTful APIs
- Implementing a complete task management API
- Handling exceptions properly
- Following best practices
With this knowledge, you should be able to build robust and well-structured web applications using Spring MVC.
Additional Resources and Exercises
Resources
Exercises
- 
Basic Controller: Create a simple controller that displays a welcome message and the current date/time. 
- 
Parameter Handling: Create a calculator controller that can perform basic operations (add, subtract, multiply, divide) based on request parameters. 
- 
RESTful API: Implement a book management API with endpoints to create, read, update, and delete books. 
- 
Exception Handling: Extend your book API to include proper exception handling for scenarios like book not found or invalid input. 
- 
Form Handling: Create a contact form controller that validates and processes form submissions, then displays a thank you page. 
Happy coding with Spring MVC Controllers!
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!