ArchitectMind
  1. Home
  2. Tutorials
  3. Book 4 · Spring & Spring Boot
  4. Volume IV
  5. MVC
Book 4 · Volume IV — Spring MVC, REST & Web Applications · Topic 1.1

MVC

Model, View and Controller — the pattern and its Spring implementation

7 min readStage 2 · Frameworks & design8 interview questions
Chapter
Chapter 1 — Spring MVC Architecture
Prerequisites
Volumes I–III; basic HTTP
You will be able to
Explain the MVC pattern, identify model, view and controller in server-side and REST applications, and design controllers that stay thin
Covers
Model · View · Controller
On this page
  1. 1. Concept / Definition
  2. 2. Why It Exists
  3. 3. Core Concepts
  4. 4. How It Works Internally
  5. 4A. Deep Dive — Keeping Controllers Thin
  6. 5. APIs / Features / Syntax
  7. 6. Practical Examples
  8. 7. Common Problems & Mistakes
  9. 8. Performance & Production Considerations
  10. 9. Architecture & Design Considerations
  11. 10. Interview Questions & Answers

1Concept / Definition

Model–View–Controller (MVC) is an architectural pattern that separates an interactive application into three responsibilities:

Model, View and Controller
Figure 1 — Model, View and Controller
RoleResponsibilitySpring MVC realisation
ModelData needed to produce the responseModel/ModelMap attributes, ModelAndView, response DTOs
ViewRendering the model into an output formatTemplate views (Thymeleaf, FreeMarker, JSP) or serialised representations (JSON via message converters)
ControllerHandling input, invoking application logic, choosing the view/response@Controller / @RestController classes with handler methods
Table 1 — MVC roles in Spring

2Why It Exists

MVC originated in Smalltalk-80 (Trygve Reenskaug, 1979) to decouple user interface presentation from application data. Web frameworks adopted a variant ("Model 2") in which a controller servlet processes requests and forwards to a view page. Spring MVC (2003+) implemented Model 2 with a front controller (DispatcherServlet) and made every part pluggable. The separation remains valuable: views can change without touching business logic, the same logic can serve HTML and JSON, and each part can be tested independently.

3Core Concepts

3.1 Model

JAVA
@Controller
@RequestMapping("/orders")
class OrderPageController {
    private final OrderQueries orders;
    OrderPageController(OrderQueries orders) { this.orders = orders; }
 
    @GetMapping
    String list(@RequestParam(defaultValue = "OPEN") OrderStatus status, Model model) {
        model.addAttribute("orders", orders.byStatus(status));   // model attribute
        model.addAttribute("status", status);
        return "orders/list";                                      // view name
    }
}
The model in Spring MVC
Figure 2 — The model in Spring MVC
Model formTypical use
Model / ModelMap parameterAdd attributes in page controllers
ModelAndView return valueView name and model returned together
@ModelAttribute methodAttributes common to all handlers of a controller (e.g. reference data)
@ModelAttribute parameterBind request parameters (forms) into an object
Response DTO (REST)The data the client receives; serialised as JSON
Table 2 — Forms of the model

3.2 View

Server-side MVC versus REST APIs
Figure 3 — Server-side MVC versus REST APIs
  • Template views: a ViewResolver maps the returned view name ("orders/list") to a template (templates/orders/list.html with Thymeleaf); the view renders model attributes into HTML.
  • Representations: with @ResponseBody (or @RestController), the return value itself is written by an HttpMessageConverter — the "view" is the JSON or XML representation, chosen by content negotiation.
HTML
<!-- templates/orders/list.html (Thymeleaf) -->
<tr th:each="o : ${orders}">
  <td th:text="${o.id}">o-1</td>
  <td th:text="${#numbers.formatDecimal(o.total, 1, 2)}">10.00</td>
</tr>

3.3 Controller

JAVA
@RestController
@RequestMapping("/api/orders")
class OrderApiController {
    private final PlaceOrderUseCase placeOrder;
    OrderApiController(PlaceOrderUseCase placeOrder) { this.placeOrder = placeOrder; }
 
    @PostMapping
    ResponseEntity<OrderView> place(@Valid @RequestBody PlaceOrderRequest req) {
        Order order = placeOrder.execute(req.toCommand());  // delegate to the use case
        return ResponseEntity.created(URI.create("/api/orders/" + order.id()))
                             .body(OrderView.from(order));  // model → representation
    }
}
Where MVC fits in a layered application
Figure 4 — Where MVC fits in a layered application

4How It Works Internally

1. The DispatcherServlet (Topic 1.2) receives every request and asks its handler mappings for a handler — typically a HandlerMethod wrapping the controller method.

2. A handler adapter (Topic 1.4) resolves the method's arguments, including a Model instance backed by a ModelAndViewContainer.

3. The controller executes and returns a view name, ModelAndView, or a body object.

4. For views, the DispatcherServlet asks ViewResolvers for a View and calls render(model, request, response); for bodies, a return value handler writes the object with a message converter and marks the request as handled (no view).

5. The model lives only for the duration of the request (unless flash attributes or session attributes are used).

4ADeep Dive — Keeping Controllers Thin

Controllers tend to accumulate logic: validation rules, orchestration, transaction boundaries, mapping, error translation. That makes them hard to test and couples business rules to HTTP.

ConcernBelongs inWhy
HTTP binding, status codes, headersControllerIt is the HTTP adapter
Input shape validation (@NotBlank, sizes)Request DTO + @ValidDeclarative, at the edge
Business rules and invariantsDomain / application serviceReusable, testable without HTTP
TransactionsApplication service (@Transactional)Use-case boundary (Volume II)
Mapping entity ↔ DTODedicated mapper or DTO factory methodsKeeps controller readable
Exception → HTTP status@ControllerAdvice (Topic 3.4)Consistent error contract
Table 3 — Assigning responsibilities
JAVA
// Too fat: rules, persistence and HTTP mixed
@PostMapping
ResponseEntity<?> place(@RequestBody Map<String, Object> body) {
    if (((List<?>) body.get("lines")).isEmpty()) {
        return ResponseEntity.badRequest().build();
    }
    var order = new OrderEntity(); /* ... 40 lines of mapping and rules ... */
    repository.save(order);
    mailer.sendConfirmation(order);
    return ResponseEntity.ok(order);          // entity leaked to the client
}

5APIs / Features / Syntax

APIPurpose
@Controller, @RestControllerController stereotypes
Model, ModelMap, ModelAndViewModel for views
@ModelAttribute, @SessionAttributes, RedirectAttributesModel binding, conversational state, flash attributes
ViewResolver, ViewView resolution and rendering
@ResponseBody, ResponseEntityRepresentation responses
spring-boot-starter-thymeleafServer-side templates
Table 4 — MVC APIs

6Practical Examples

6.1 Post/Redirect/Get with flash attributes

JAVA
@PostMapping("/orders")
String submit(@Valid @ModelAttribute OrderForm form, BindingResult errors,
              RedirectAttributes redirect) {
    if (errors.hasErrors()) return "orders/new";                 // redisplay form
    Order o = placeOrder.execute(form.toCommand());
    redirect.addFlashAttribute("message", "Order " + o.id() + " placed");
    return "redirect:/orders/" + o.id();                         // avoid double submit
}

6.2 Same use case, two views

JAVA
@GetMapping(value = "/orders/{id}", produces = MediaType.TEXT_HTML_VALUE)
String page(@PathVariable String id, Model model) {
    model.addAttribute("order", queries.byId(id));
    return "orders/detail";
}
 
@GetMapping(value = "/orders/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody OrderView json(@PathVariable String id) { return queries.byId(id); }

6.3 Common model attributes

JAVA
@ModelAttribute("statuses")
// available to every view of this controller
OrderStatus[] statuses() { return OrderStatus.values(); }

6.4 Testing a page controller

JAVA
@WebMvcTest(OrderPageController.class)
class OrderPageControllerTest {
    @Autowired MockMvcTester mvc;
    @MockitoBean OrderQueries orders;
 
    @Test
    void rendersList() {
        given(orders.byStatus(OrderStatus.OPEN)).willReturn(List.of(sample()));
        assertThat(mvc.get().uri("/orders")).hasStatusOk()
                .hasViewName("orders/list").model().containsKey("orders");
    }
}

6.5 View technologies at a glance

TechnologyStarterCharacteristics
Thymeleafspring-boot-starter-thymeleafNatural HTML templates, strong Spring integration
FreeMarkerspring-boot-starter-freemarkerMature template language, also for emails
Mustachespring-boot-starter-mustacheLogic-less, simple
JSP(servlet container support)Legacy; not supported with executable jars — avoid for new apps
JSON/XML representationsspring-boot-starter-webmvc + JacksonREST APIs; view = serialised model
Table 5 — View technologies supported by Spring Boot

6.6 Classic MVC versus MVC in a REST API

AspectClassic (server-side)REST API
Controller outputView name + modelObject / ResponseEntity
View selectionViewResolverContent negotiation + message converters
Model lifetimeRequest (+ flash/session)Request only
StateOften session-based (login, forms)Stateless, token-based
Error presentationError pages / form errorsProblem Details JSON
Table 6 — Comparing the two MVC styles

7Common Problems & Mistakes

  • Business logic in controllers.
  • Entities as models or response bodies.
  • Stateful controllers (fields holding request data) — controllers are singletons.
  • Mixing HTML and JSON concerns in one handler without produces.
  • Forgetting Post/Redirect/Get — duplicate form submissions on refresh.

8Performance & Production Considerations

ConcernGuidance
Template renderingEnable template caching in production (default)
Model sizeOnly what the view needs — large models cost memory and render time
DTO mappingCheap compared with I/O; avoid reflection-heavy mapping in hot paths
Session attributesIncrease memory per user; avoid for stateless APIs
Table 7 — Production guidance

9Architecture & Design Considerations

  • Controllers are adapters in hexagonal architecture: HTTP in, use case call, HTTP out.
  • Separate API DTOs from domain and persistence models.
  • Choose server-side rendering vs SPA + API per product needs; both run on the same MVC core.
  • Consistent controller conventions across teams (naming, DTOs, error handling).

10Interview Questions & Answers

Q1Basic

What does MVC stand for?

MVC stands for Model–View–Controller, a pattern for separating responsibilities in applications with a user interface. The model holds the data and state to be displayed, the view renders that model into a representation such as an HTML page or a JSON document, and the controller receives user input or HTTP requests, calls the application's business logic, and decides which model and view to use. In Spring MVC, the DispatcherServlet routes each request to a controller method, and the result is rendered by a view or converted to JSON. The separation keeps presentation, request handling and business logic independently changeable.

Q2Basic

What is the "view" in a REST API built with Spring?

In a REST API there is no HTML template; the view is the serialised representation of the object the controller returns. When a @RestController method returns an OrderResponse, Spring selects an HttpMessageConverter based on the request's Accept header, usually the Jackson converter, and writes the object to the response body as JSON. Content negotiation can produce other formats, such as XML, from the same model. This means the shape of returned objects is the API contract, so it should be designed deliberately with dedicated response DTOs, rather than exposing whatever internal objects happen to be convenient.

Q3Intermediate

How does a controller return a server-rendered page?

A controller annotated with @Controller adds data to a Model object, for example model.addAttribute("orders", orders), and returns a logical view name such as "orders/list". Spring's ViewResolver maps that name to a template, for example a Thymeleaf file at templates/orders/list.html, and the template engine renders HTML using the model attributes. Because the controller returns only a name, the template technology can change without changing the controller. Forms work the other way: submitted fields are bound to an object annotated with @ModelAttribute, validated with @Valid, and errors are shown in the same template.

Q4Intermediate

Why should controllers not return JPA entities?

Returning JPA entities couples your API to your database schema: any change to the entity, such as renaming a field or adding a relationship, changes the JSON clients receive. It can expose internal fields such as audit columns or passwords. Serialising an entity may trigger lazy loading of associations, causing extra queries, LazyInitializationException errors or very large responses through bidirectional relationships. It also tempts clients to send entities back, which allows them to change fields they should not. Use dedicated request and response DTOs, often records, and map explicitly; this keeps the API stable while the persistence model evolves.

Q5Senior Engineer

What is Post/Redirect/Get and why use it?

Post/Redirect/Get is a pattern for handling form submissions in server-rendered applications. After a successful POST, such as placing an order, the controller does not render a page directly; it returns a redirect, for example redirect:/orders/42, and the browser then issues a GET to that URL. Messages such as "Order placed" can be passed with flash attributes, which survive exactly one redirect. If the user refreshes the resulting page, the browser repeats the harmless GET instead of resubmitting the form, which avoids duplicate orders or payments. It also gives each result page a bookmarkable URL.

Q6Senior Engineer

Where should validation and business rules live?

Separate the two kinds of rule. Validation of the input's shape belongs at the boundary: required fields, formats, lengths and ranges, declared with Bean Validation annotations on request DTOs and triggered with @Valid in the controller, so bad requests are rejected early with a 400 response. Business rules, such as whether a customer may place an order or whether stock is available, belong in the domain or application service layer. They must apply no matter how the service is called, whether from HTTP, messaging or a batch job, and they should be testable without a web container.

Q7Technical Architect

How does MVC relate to hexagonal architecture?

In hexagonal architecture, the application core, with its domain model and use cases, sits in the middle and talks to the outside world through ports. Adapters connect those ports to specific technologies. A Spring MVC controller is an inbound adapter: it translates an HTTP request into a call on a use case, such as placeOrder, and translates the result back into an HTTP response. It should contain no business logic. Keeping controllers this thin means the core does not depend on the web framework, so the same use cases can also be driven by message listeners, batch jobs or tests.

Q8Solution Architect

When would you choose server-side rendering over an SPA with a REST API?

Choose by the kind of application and the team. Server-side rendering, with Spring MVC and Thymeleaf or a similar engine, suits content-heavy and form-heavy applications with modest interactivity, applications where search-engine visibility matters, and small teams who benefit from one code base and simple deployment. A single-page application calling a REST API suits highly interactive user interfaces, offline capabilities, and situations where several clients, such as web and mobile apps or partners, share the same API. Many systems mix them, and approaches such as HTMX add interactivity to server-rendered pages without a full SPA.

Newsletter

Get new tutorials every week

New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.

Almost done! Check your inbox and click the confirmation link to complete your subscription.
Keep going in Book 4

Spring & Spring Boot

Volume I · Topic 1.1

Why Spring Exists

Spring Core, IoC & Dependency Injection

Volume I · Topic 1.2

Spring Framework Architecture

Spring Core, IoC & Dependency Injection

Volume I · Topic 1.3

Spring Modules

Spring Core, IoC & Dependency Injection

Volume II · Topic 1.1

Cross-Cutting Concerns

Spring AOP, Proxies, Events & Transactions

Volume II · Topic 1.2

Why Cross-Cutting Concerns Are Difficult

Spring AOP, Proxies, Events & Transactions

Volume II · Topic 1.3

Aspect-Oriented Programming

Spring AOP, Proxies, Events & Transactions

Volume III · Topic 1.1

Why Spring Boot Exists

Spring Boot, Configuration & Application Lifecycle

Volume III · Topic 1.2

Spring Boot Architecture

Spring Boot, Configuration & Application Lifecycle

Volume III · Topic 1.3

Spring Boot vs Spring Framework

Spring Boot, Configuration & Application Lifecycle

Volume IV · Topic 1.2

DispatcherServlet

Spring MVC, REST & Web Applications

Volume IV · Topic 1.3

MVC Request Lifecycle

Spring MVC, REST & Web Applications

Volume V · Topic 1.1

Repository Abstraction

Spring Data, JPA & Persistence

Volume V · Topic 1.2

Spring Data Architecture

Spring Data, JPA & Persistence

Volume V · Topic 1.3

Repository Types

Spring Data, JPA & Persistence

Volume VI · Topic 1.1

Security Architecture

Spring Security, OAuth2 & Identity

Volume VI · Topic 1.2

Security Filter Chain

Spring Security, OAuth2 & Identity

Volume VI · Topic 1.3

Security Context

Spring Security, OAuth2 & Identity

Volume VII · Topic 1.1

Enterprise Integration

Spring Integration, Testing & Cloud-Native Development

Volume VII · Topic 1.2

Integration Patterns

Spring Integration, Testing & Cloud-Native Development

Volume VII · Topic 1.3

Messaging Systems

Spring Integration, Testing & Cloud-Native Development