Command Palette

Search for a command to run...

PHASE 2Beginner ~16 min· topic 27 of 28Enterprise

Enterprise 4 — MVC and Front Controller

In one line

Model-View-Controller separates data and rules (model), presentation (view) and request handling (controller). A Front Controller is a single entry point that receives every request, applies shared steps (auth, logging), and dispatches to the right controller: Spring MVC's DispatcherServlet.

0/28 · 0%

Think of it like this

A restaurant. The kitchen prepares food (model), the plating and menu card decide how it looks (view), and waiters take orders and bring results (controllers). A single host at the door greets every guest, checks reservations and sends each party to the right waiter: the front controller.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

MVC
Model-View-Controller: splitting an application into data/rules, presentation, and request handling.
Front controller
One component that receives every request, runs shared steps and forwards to the right handler.
Handler
The method that handles one route, such as GET /orders/{id}.
Interceptor / filter
Code that runs before or after handlers for every request, such as authentication or logging.

Step by step

01The problem: every endpoint repeats the same plumbing

In an early Tiffin prototype, each servlet checked the auth header, logged the request, caught exceptions and formatted errors itself. One forgot the auth check; another returned stack traces to users. Business logic was mixed into the same methods.

02A tiny front controller

One dispatch method handles cross-cutting work once, then looks up the handler for the route. Handlers (controllers) only translate between HTTP and the model.

FrontController.javawhole filejava
import java.util.*;
import java.util.function.Function;

record Request(String method, String path, String token) {}
record Response(int status, String body) {}

final class OrderModel {                                   // model: data + rules
  private final Map<String, String> status = new HashMap<>(Map.of("o1", "PREPARING"));
  Optional<String> statusOf(String id) { return Optional.ofNullable(status.get(id)); }
}

final class FrontController {
  private final Map<String, Function<Request, Response>> routes = new HashMap<>();
  private final List<String> log = new ArrayList<>();

  void register(String method, String prefix, Function<Request, Response> handler) { routes.put(method + " " + prefix, handler); }

  Response dispatch(Request req) {
    log.add(req.method() + " " + req.path());                              // shared step: logging
    if (!"valid".equals(req.token())) return new Response(401, "{\"error\":\"unauthorised\"}");   // shared step: auth
    String key = req.method() + " /" + req.path().split("/")[1];
    Function<Request, Response> handler = routes.get(key);
    if (handler == null) return new Response(404, "{\"error\":\"no route\"}");
    try { return handler.apply(req); }
    catch (RuntimeException e) { return new Response(500, "{\"error\":\"internal\"}"); }   // shared step: errors
  }

  int requestsSeen() { return log.size(); }
}

final class OrderController {                              // controller: HTTP <-> model, view = JSON text
  private final OrderModel model;
  OrderController(OrderModel model) { this.model = model; }
  Response get(Request r) {
    String id = r.path().split("/")[2];
    return model.statusOf(id)
        .map(s -> new Response(200, "{\"id\":\"" + id + "\",\"status\":\"" + s + "\"}"))
        .orElse(new Response(404, "{\"error\":\"order not found\"}"));
  }
}

03The same shape in Spring MVC

DispatcherServlet receives every request; servlet filters and HandlerInterceptors run shared steps (Spring Security's filter chain handles authentication); @RequestMapping/@GetMapping annotations build the route table; @ControllerAdvice with @ExceptionHandler centralises errors; and message converters (Jackson) render the model as JSON, which is the view for an API.

The same shape in Spring MVCdiagram
Rendering diagram…

Try it yourself

  1. 1

    Dispatch three requests

    A valid lookup, a missing token, and an unknown route. Every request is logged by the front controller.

    terminal
    $ jshell> /open FrontController.java
    jshell> var fc = new FrontController(); var orders = new OrderController(new OrderModel())
    jshell> fc.register("GET", "/orders", orders::get)
    jshell> fc.dispatch(new Request("GET", "/orders/o1", "valid"))
    jshell> fc.dispatch(new Request("GET", "/orders/o1", null))
    jshell> fc.dispatch(new Request("GET", "/menu/today", "valid")).status() + " after " + fc.requestsSeen() + " requests"
    ── expected output ──
    $5 ==> Response[status=200, body={"id":"o1","status":"PREPARING"}]
    $6 ==> Response[status=401, body={"error":"unauthorised"}]
    $7 ==> "404 after 3 requests"

In your stack

  • →

    Spring MVC: DispatcherServlet (front controller), @RestController, HandlerInterceptor, @ControllerAdvice, HttpMessageConverter (views for APIs), Thymeleaf for HTML views. Jakarta EE has the same roles with servlets, filters and JAX-RS resources.

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Business rules hidden in a controller

The "free delivery above ₹499" rule is implemented inside CheckoutController. A new Kafka consumer that places scheduled orders calls the service directly.

terminal
$ psql -c "select source, count(*) filter (where delivery_fee > 0 and subtotal >= 49900) as wrongly_charged from orders where placed_at > now() - interval '1 day' group by source;"
── what you'll see ──
source | wrongly_charged
-----------+-----------------
web | 0
scheduled | 1284

Myth vs fact

Myth

MVC is only for server-rendered web pages.

Fact

REST APIs use the same split: the controller handles HTTP, the model holds the rules, and the JSON representation is the view.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Treat every entry point (HTTP, messaging, scheduled jobs, CLI) as a thin adapter over the same application services: this is the core of hexagonal (ports and adapters) architecture, and it's what makes adding a new channel cheap.

Remember this

  1. 1

    Model: domain objects and business logic. View: how a response is presented (an HTML template, or a JSON document for APIs). Controller: reads the request, calls the model, chooses the view or response.

  2. 2

    The separation lets each part change independently: a new UI or a mobile API reuses the same model and rules.

  3. 3

    A Front Controller centralises what every request needs (authentication, logging, error handling, content negotiation) and then dispatches by URL and method to a handler. In Spring MVC, DispatcherServlet is the front controller and your @Controller/@RestController methods are the handlers.

  4. 4

    Shared steps before and after handlers are usually a chain (filters, interceptors: Chain of Responsibility, Behavioral 6), and the dispatch table is a map from route to handler.

  5. 5

    Keep controllers thin: parse, validate, call one application service, map the result. Business rules in controllers can't be reused by other entry points such as message consumers or scheduled jobs.

Explain it without notes

01

What does each part of MVC own?

02

What does a front controller centralise, and what does Spring use for it?

Practice

01

Add a POST /orders/{id}/cancel route to the tiny front controller that only cancels orders still PREPARING.

02

Where should request validation (missing fields, wrong types) live versus business validation (can this order be cancelled)?

Trade-offs

  • ↔

    Clear separation, consistent cross-cutting behaviour and reusable rules, versus more layers and indirection for very small applications.

Done when you can

  • I can explain MVC for both HTML pages and JSON APIs.

  • I can describe how DispatcherServlet acts as a front controller and where shared concerns plug in.

Back to phase