What is Spring MVC: @Controllers & @RestControllers - header image

What is Spring MVC: @Controllers & @RestControllers

Last updated on June 12, 2020 -

Who the hell am I?

I'm @MarcoBehler and I share everything I know about making awesome software through my guides, screencasts, talks and courses.

Follow me on Twitter to find out what I'm currently working on.

You can use this guide to understand what Spring MVC is, how its @Controllers, @RestControllers and DispatcherServlet work. Also, how it compares to Spring Boot.

(Editor’s note: At ~5000 words, you probably don’t want to try reading this on a mobile device. Bookmark it and come back later.)

Introduction

What is Spring MVC?

Spring MVC is Spring’s web framework. It lets you build web sites or RESTful services (think: JSON/XML) and is nicely integrated into the Spring ecosystem, e.g. it powers the @Controllers and @RestControllers of your Spring Boot applications.

That doesn’t really help, does it?

Luckily, there’s also a long answer: The remainder of this document.

(If you are unsure of what Spring or Spring Boot is, you might want to read What Is Spring Framework?, first.)

MVC Basics: HttpServlets

When writing web applications in Java, with or without Spring (MVC/Boot), you are mostly talking about writing applications that return two different data formats:

  1. HTML → Your web app creates HTML pages that can be viewed in a browser.

  2. JSON/XML → Your web app provides RESTful services, that produce JSON or XML. Javascript-heavy websites or even other web services can then consume the data that these services provide.

  3. (Yes, there’s other data formats and use cases as well, but we’ll ignore them for now.)

How would you write such applications without any framework? Just with plain Java?

Answering this question is essential to really understanding Spring MVC, so do not just skip ahead because you think it has nothing to do with Spring MVC.

Answer

At the lowest level, every Java web application consists of one or more HttpServlets. They generate your HTML, JSON, or XML.

In fact, (almost) every single framework of the 1 million available Java web frameworks (Spring MVC, Wicket, Struts) is built on top of HttpServlets.

How to write HTML pages with HttpServlets

Let’s have a look at a super simple HttpServlet that returns a very simple, static, HTML page.

package com.marcobehler.springmvcarticle;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyServletV1 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (req.getRequestURI().equals("/")) {
            resp.setContentType("text/html");
            resp.getWriter().print("<html><head></head><body><h1>Welcome!</h1><p>This is a very cool page!</p></body></html>");
        }
        else {
            throw new IllegalStateException("Help, I don't know what to do with this url");
        }
    }
}

Let’s break this down.

public class MyServletV1 extends HttpServlet {

Your servlet extends Java’s HttpServlet class.

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

To handle (any) GET request, you need to override the doGet() method from the superclass. For POST requests you would override doPost(). Similarly, for all other HTTP methods.

if (req.getRequestURI().equals("/")) {

Your servlet needs to make sure that the URL that comes in is a request that it knows how to handle. For now, the servlet only handles "/", i.e.: it handles www.marcobehler.com, but NOT www.marcobehler.com/hello.

resp.setContentType("text/html");

You need to set the proper Content-Type on the ServletResponse to let the browser know what content you are sending. In this case, it’s HTML.

resp.getWriter().print("<html><head></head><body><h1>Welcome!</h1><p>This is a very cool page!</p></body></html>");

Remember: web sites are just HTML strings! So you need to generate an HTML string, any way you want, and send that back with the ServletResponse. One way of doing that is with the response’s writer.

After writing your servlet, you would register it with a servlet container, like Tomcat or Jetty. If you are using an embedded version of either servlet container, all the code needed to run your servlet would look like this:

package com.marcobehler.springmvcarticle;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Wrapper;
import org.apache.catalina.startup.Tomcat;

public class TomcatApplicationLauncher {

    public static void main(String[] args) throws LifecycleException {
        Tomcat tomcat = new Tomcat();
        tomcat.setPort(8080);
        tomcat.getConnector();

        Context ctx = tomcat.addContext("", null);
        Wrapper servlet = Tomcat.addServlet(ctx, "myServlet", new MyServletV2());
        servlet.setLoadOnStartup(1);
        servlet.addMapping("/*");

        tomcat.start();
    }
}

Let’s break this down.

Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
tomcat.getConnector();

You configure a new Tomcat server which will start on port 8080.

Context ctx = tomcat.addContext("", null);
Wrapper servlet = Tomcat.addServlet(ctx, "myServlet", new MyServletV2());

This is how you register your Servlet with Tomcat. This is the first part, where you simply tell Tomcat about your servlet.

servlet.addMapping("/*");

The second part is letting Tomcat know for what requests the servlet is responsible, i.e. the mapping. A mapping of /* means it’s responsible for any incoming request (/users, /register, /checkout).

tomcat.start();

That’s it. You run your main() method now, go to port 8080 in your favorite web browser (http://localhost:8080/), and you’ll see a nice HTML page.

So, essentially, as long as you keep extending your doGet() and doPost() methods, your whole web application could consist of just one servlet. Let’s try that out.

How to write JSON endpoints with HttpServlets

Imagine that apart from your (pretty empty) HTML index page, you now also want to offer a REST API for your soon-to-be-developed front end. So your React or AngularJS front end would call a URL like this:

/api/users/{userId}

That endpoint should return data in JSON format for the user with the given userId. How could we enhance our MyServlet to do this, again, no frameworks allowed?

package com.marcobehler.springmvcarticle;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyServletV2 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (req.getRequestURI().equals("/")) {
            resp.setContentType("text/html");
            resp.getWriter().print("<html><head></head><body><h1>Welcome!</h1><p>This is a very cool page!</p></body></html>");
        } else if (req.getRequestURI().startsWith("/api/users/")) {

            Integer prettyFragileUserId = Integer.valueOf(req.getRequestURI().lastIndexOf("/") + 1);

            resp.setContentType("application/json");

            // User user = dao.findUser(prettyFragileUserId)
            // actually: jsonLibrary.toString(user)
            resp.getWriter().print("{\n" +
                    "  \"id\":" + prettyFragileUserId + ",\n" +
                    "  \"age\": 55,\n" +
                    "  \"name\" : \"John Doe\"\n" +
                    "}");
        } else {
            throw new IllegalStateException("Help, I don't know what to do with this url");
        }
    }
}

Let’s break this down.

} else if (req.getRequestURI().startsWith("/api/users/")) {

We add another if to our doGet method, to handle the /api/users/ calls.

Integer prettyFragileUserId = Integer.valueOf(req.getRequestURI().lastIndexOf("/") + 1);

We do some extremely fragile URL parsing. The last part of the URL is the userID, e.g. 5 for /api/users/5. We just assume here that the user always passes in a valid int, which you would actually need to validate!

resp.setContentType("application/json");

Writing JSON to the browser means setting the correct content-type.

// User user = dao.findUser(prettyFragileUserId)
// actually: jsonLibrary.toString(user)
resp.getWriter().print("{\n" +
        "  \"id\":" + prettyFragileUserId + ",\n" +
        "  \"age\": 55,\n" +
        "  \"name\" : \"John Doe\"\n" +
        "}");

Again, JSON is just text, so we can write that directly to the HTTPServletResponse. you would probably use a JSON library to convert our User Java object to this string, but for the sake of simplicity, I won’t show that here.

The problem with our One-Servlet-To-Rule-Them-All Approach

While our servlet above works, there are quite a few problems on the horizon:

  1. Your Servlet needs to do a lot of manual HTTP-specific plumbing, checking request URIs, fumbling with strings, etc. In other words: it needs to know WHAT the users want to do.

  2. It then also needs to find the data for whatever you want to display. In other words: it needs to know the HOW. In our example above, that would be finding the user in a database, which we conveniently commented-out.

  3. It then also needs to convert that data to JSON or to HTML and set the appropriate response types.

Quite a lot of different responsibilities, eh? Wouldn’t it be nicer if you didn’t have to care about all that plumbing? No more request URI and parameter parsing, no more JSON conversions, no more servlet responses?

That’s exactly where Spring MVC comes in.

What is Spring MVC’s DispatcherServlet?

What if I told you that Spring MVC is really just one servlet, like our uber-servlet above? (And yes, that’s of course a bit of a lie)

Meet the DispatcherServlet.

Spring MVC’s DispatcherServlet handles every incoming HTTP request (that’s it is also called front controller). Now, what does handle mean, exactly?

A sample HTTP request flow

Imagine a "register user workflow", where a user fills out a form and submits it to the server to get a nice little success HTML page back.

In that case, your DispatcherServlet needs to do the following things:

  1. It needs to have a look at the incoming HTTP method request URI and any request parameters. E.g.: POST /register?name=john&age33.

  2. It needs to potentially convert the incoming data (request parameters/body) to nice little Java objects and forward them to a @Controller or @RestController class that you wrote.

  3. Your @Controller method saves a new user to the database, maybe sends out an email, etc. It would highly likely delegate that to another service class, but let’s assume for now this happens inside the controller.

  4. It needs to take whatever the output from your @Controller was and convert it back to HTML/JSON/XML.

DispatcherServlet Overview

The whole process looks like this, with a fair number of intermediary classes neglected because DispatcherServlet doesn’t do all the work itself.

dispatcher servlet1d

What’s a ModelAndView in the above graphic? How exactly does the DispatcherServlet convert the data?

It is easiest to understand by looking at a real-life example. For example: How do you write HTML websites with Spring MVC? Let’s find out in the next section.

How to write HTML with @Controllers

Whenever you want to write HTML to a client like a browser with Spring MVC (and that includes Spring Boot), you’ll want to write a @Controller class. Let’s do that now.

How to write a @Controller in Spring

For our user registration workflow from above (POST /register?name=john&age33), you would write the following class.

package com.marcobehler.springmvcarticle;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class RegistrationController {

    @PostMapping("/register")
    public String registerUser(@RequestParam(required = false) Integer age, @RequestParam String name, Model model) {
        User user = new User(name, age);

        // TODO save user to database
        // userDao.save(user);

        // TODO send out registration email
        // mailService.sendRegistrationEmail(user);

        model.addAttribute("user", user);
        return "registration-success";
    }
}

Let’s break this down.

@Controller
public class RegistrationController {

A controller class in Spring is simply annotated with the @Controller annotation, it does not need to implement a specific interface or extend from another class.

@PostMapping("/register")

This line tells our DispatcherServlet that whenever a POST request comes in for the path /register, including any request parameters (e.g. ?username=), it should dispatch the request to this very controller method.

public String registerUser(@RequestParam(required = false) Integer age, @RequestParam String name, Model model) {

Note The naming of our method (registerUser) does not really matter, it could be called anything.

We do however specify that each request should include two request parameters, which could either be part of the URL (?age=10&name=Joe) or be in the POST request body. Furthermore, only the name parameter is required (the age parameter is optional)

And the age parameter, if the user supplies it, is automatically converted to an Integer (an exception is thrown if the supplied value is not a valid Integer)

Last, but not least, Spring MVC automatically injects a model parameter into our controller method. That model is a simple map where you need to put all the data that you want to show in your final HTML page, but more on that in a second.

User user = new User(name, age);

// TODO save user to database
// userDao.save(user);

// TODO send out registration email
// mailService.sendRegistrationEmail(user);

You do whatever you need to do with the incoming request data. Create a user, save it to a database, send out an email. This is your business logic.

model.addAttribute("user", user);

You add your user to the model, under key "user". Which means, you’ll be able to reference it in your HTML template later on, like "${user.name}". More on that in a second.

return "registration-success";

Your method returns a simple string, with the value registration-success. This is not just any string, it is a reference to your view, i.e. the HTML template that you want Spring to render.

What do views look like?

Let’s ignore for now how (or rather where) Spring MVC will try and find that view, i.e. your template; instead, let’s see what your registration-success.html template should look like.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<p th:text="'Hello ' + ${user.name} + '!'"></p>

</body>
</html>

It’s just a simple HTML page, which contains one template-y line. It prints out the name of the user that has just registered.

<p th:text="'Hello ' + ${user.name} + '!'"></p>

The question is, what is this th:text= syntax? Is that Spring-specific? Is it something else?

And the answer is that Spring MVC doesn’t really know anything about HTML templates. It needs a 3rd-party templating library to do all HTML templating work and doesn’t necessarily care what library you choose.

In the above case, you are looking at a Thymeleaf template, which is a very popular choice when working on Spring MVC projects.

Spring MVC & Templating Libraries

There are several different templating libraries that integrate well with Spring MVC that you can choose from: Thymeleaf, Velocity, Freemarker, Mustache and even JSP (even though that is not a templating library).

In fact, you must explicitly choose a templating library, because if you do not have such a templating library added to your project and configured correctly, then your @Controller method would not render your HTML page - because it wouldn’t know how to do it.

It also means that you have to learn and understand the syntax of the particular templating library depending on the project you’re, in because they’re all slightly different from each other. Fun, right?

What is a ViewResolver?

For a second, let’s think about where Spring will actually try and find your HTML templates that your @Controller returns.

The class that tries to find your template is called a ViewResolver. So whenever a request comes into your Controller, Spring will have a look at the configured ViewResolvers and ask them, in order, to find a template with the given name. If you don’t have any ViewResolvers configured, this won’t work.

Imagine you want to integrate with Thymeleaf. Hence you would need a ThymeleafViewResolver.

package com.marcobehler.springmvcarticle;

import org.springframework.context.annotation.Bean;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;

public class ThymeleafConfig {

    @Bean
    public ThymeleafViewResolver viewResolver() {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();

        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setPrefix("classpath:/templates");
        templateResolver.setSuffix(".html");
        // some other lines neglected...

        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        // some other lines neglected...

        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }


}

Let’s break this down.

@Bean
public ThymeleafViewResolver viewResolver() {
    ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();

In the end, a ThymeleafViewResolver simply implements Spring’s ViewResolver interface. Given a template name (remember: registration-success), ViewResolvers can find the actual template.

SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();

The ThymeleafViewResolver needs a couple of other Thymeleaf-specific classes to work properly. One of these classes is the SpringResourceTemplateResolver. It does the actual work of finding your template.

Note
SpringResourceTemplateResolver is a Thymeleaf class
templateResolver.setPrefix("classpath:/templates");
templateResolver.setSuffix(".html");

You are basically saying (with help of the Spring Resources syntax): "All my templates are on the classpath, in the /templates folder". And, by default, they all end with .html. This means:

Whenever our @Controller returns a String like registration-success, the ThymeleafViewResolver will look for a template: classpath:/templates/registration-success.html.

Side Note: Spring MVC, Spring Boot & Controllers

You might be thinking: Marco, I’ve never had to configure such a ViewResolver in my entire life working on Spring Boot projects. And that is correct. Because Spring Boot automatically configures one for you, whenever you add a dependency such as spring-boot-starter-thymeleaf to your project.

It also configures the ViewResolver to have a look at your src/main/resources/template directory, by default.

So, Spring Boot really just pre-configures Spring MVC for you. Keep that in mind.

Summary: Model-View-Controller

Having seen a complete @Controller & ViewResolver example makes it much easier to talk about Spring’s Model-View-Controller concept.

  • With a couple of annotations (@Controller, @PostMapping, @RequestParam) you can write a controller that takes care of receiving request data and processes it accordingly.

  • Your model contains all the data (and just the data) that you want to render in your view. It is your job to fill that model map.

  • Your view is just an HTML template. It does not care about where you got the (model) data from. Or what the current HTTP request is. Or even whether you have an active HTTP Session or not.

It is all about separation of concerns.

While a bit annotation-heavy at first sight, our Spring @Controller class reads a lot better, with a lot less HTTP plumbing involved than our uber-servlet from the beginning.

More on @Controllers

We already saw a bit of the convenience that Spring MVC gives us when handling HTTP inputs.

  • You do not have to fumble with the requestURI, you can use an annotation instead.

  • You do not have to fumble with request parameter type conversions or if a parameter is optional or required, you can use an annotation instead.

Let’s have a look at the most common annotations that help you process incoming HTTP requests.

@GetMapping & @RequestMappping

You already saw the @GetMapping annotation above. It is equal to the `@RequestMapping` annotation. Let’s see how:

@GetMapping("/books")
public void book() {
        //
}

/* these two mappings are identical */

@RequestMapping(value = "/books", method = RequestMethod.GET)
public void book2() {

}

@GetMapping, @[Post|Put|Delete|Patch]Mapping is equivalent to @RequestMapping(method=XXX). It is simply a newer way (Spring 4.3+) of specifying a mapping, so you’ll find the @RequestMapping annotation used a lot in older, legacy Spring projects.

@RequestParam

For HTTP request parameters, be that in your URL (?key=value) or in a submitted form request body, can be read in via the @RequestParam annotation.

You already saw that it does basic type conversion (e.g. from HTTP String parameter to an int) as well as checking for required or optional parameters.

@PostMapping("/users")   /* First Param is optional */
public User createUser(@RequestParam(required = false) Integer age, @RequestParam String name) {
   // does not matter
}

If you forget to provide a required parameter with your request, you’ll get a 400 Bad Request response code and, if using Spring Boot, a default error object that looks like this:

{"timestamp":"2020-04-26T08:34:34.441+0000","status":400,"error":"Bad Request","message":"Required Integer parameter 'age' is not present","path":"/users"}

If you want even more convenience, you can let Spring directly convert all @RequestParams to an object, without any needed annotations. Simply specify your object as `method parameter`.

You just need to make sure your class has corresponding getters/setters.

@PostMapping("/users")   /* Spring will convert this automatically if you have getters and setters */
public User createUser(UserDto userDto) {
    //
}

@PathVariable

Next to request parameters, another popular way of specifying variables would be directly in the request URI, as a @PathVariable. So for getting a user profile with userId=123, you would call the following URL: GET /users/123

@GetMapping("/users/{userId}")  // (1)
public User getUser(@PathVariable String userId) {
    // ...
    return user;
}
  1. You just need to make sure that your parameter value matches the one between the {} in your request mapping annotation.

In addition, PathVariables can also be required or optional.

@GetMapping("/users/{userId}")
   public User getUser(@PathVariable(required = false) String userId) {
       // ...
       return user;
   }

And PathVariables could, of course, be directly translated to a Java object (provided, the object has matching getters/setters).

@GetMapping("/users/{userId}")
public User getUser(UserDto userDto) {
    // ...
    return user;
}

Summary: @Controllers

In short, when writing HTML pages with Spring MVC you’ll have to do just a few things:

  1. Write your @Controllers, sprinkled with a couple of annotations. Spring will take care to present you the request input (request params, path variables) in a convenient manner.

  2. Execute whatever logic you need to fill your model. You can conveniently inject the model into any controller method.

  3. Let your @Controller know which HTML template you want rendered and return the template’s name as a string.

  4. Whenever a request comes in, Spring will make sure to call your controller method, and take the resulting model and view, render that to an HTML string and return it back to the browser.

  5. (Provided of course, you set up an appropriate templating library, which Spring Boot will automatically do for you, as long as you add the needed dependencies to your project.)

That’s it.

How to write XML/JSON with @RestControllers

Things are a bit different when you are writing RESTFul services. Your client, be that a browser or another web service, will (usually) create JSON or XML requests. The client sends, say, a JSON request, you process it, and then the sender expects JSON back.

So, a sender might send you this piece of JSON as part of the HTTP request body.

POST http://localhost:8080/users

###
{"email": "angela@merkel.de"}

But on the Java side (in your Spring MVC program) you do not want to mess with raw JSON strings. Neither when receiving requests like above, nor when sending responses back to the client.

Instead, you’d like to just have Java objects that get converted automatically, by Spring.

public class UserDto {
    private String email;
    //...
}

This also means that you do not need all that model and view processing that you had to do when rendering HTML in your @Controllers. For RESTful services you don’t have a templating library reading in an HTML template and filling it with model data to generate a JSON response for you.

Instead, you want to go directly from HTTP Request → Java object and from Java Object → HTTP response.

As you might have guessed, that’s exactly what Spring MVC allows you to do, by writing @RestControllers.

How to write @RestControllers

The first thing you need to do to output XML/JSON is to write a @RestController instead of a @Controller. (Although @RestController IS a @Controller, see FAQ for what the exact difference is).

If we were to write a REST Controller for a bank, that returns a user’s transaction list, it could look something like this:

package com.marcobehler.springmvcarticle;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.List;

@RestController
public class BankController {

    @GetMapping("/transactions/{userId}")
    public List<Transaction> transactions(String userId) {

        // find transaction by user
        // List<Transaction> = dao.findByUserId(userId);

        List<Transaction> transactions = Collections.emptyList();
        return transactions;
    }
}

Let’s break it down.

@RestController
public class BankController {

You annotated the BankController class with the @RestController annotation, which signals Spring that you do not want to write HTML pages through the usual ModelAndView process.

Instead, you want to write XML/JSON (or some other format) directly into the HTTP response body.

public List<Transaction> transactions(String userId) {

Your controller does not return a String (view) anymore. Instead, it returns a List<Transaction>, that you want Spring to convert to an appropriate JSON or XML structure. You basically want your Transaction Java objects to become this (someone was hungry for fast food very early in the morning):

[
  {
    "occurred": "28.04.2020 03:18",
    "description": "McDonalds - Binging",
    "id": 1,
    "amount": 10
  },
  {
    "occurred": "28.04.2020 06:18",
    "description": "Burger King - Never enough",
    "id": 2,
    "amount": 15
  }
]

But how would Spring MVC know that your transaction list should get converted to JSON? Why not XML? Or YAML? How does your @RestController method know what the supposed response format should be?

For that, Spring has the concept of Content Negotiation.

How (Response) Content Negotiation works: Accept Header

In short, content negotiation means that the client needs to tell your server what response format it wants to get back from your @RestController.

How? By specifying the Accept header with the HTTP request.

GET http://localhost:8080/transactions/{userid}
Accept: application/json

Spring MVC will have a look at that Accept header and know: The client wants JSON (application/json) back, so I need to convert my List<Transaction> to JSON. (Quick note: there are other ways to do content negotiation, but the Accept header is the default way.)

Let’s call this response content negotiation, because it is about the data format of the HTTP response that you are sending back to your client.

But content negotiation also works for incoming requests. Let’s see how.

How Request Content Negotiation works: Content-Type Header

When building RESTful APIs, there’s an extremely high chance that you also want your clients to be able to send in JSON, or XML. Let’s pick up the example from the beginning of the chapter again, where you offer a REST endpoint to register new users:

POST http://localhost:8080/users

###
{"email": "angela@merkel.de"}

How would Spring know that the request body above contains JSON and not XML or YAML?

You might have guessed right, you’ll need to add another header, this time it’s the Content-Type header.

POST ...

Content-Type: application/json; charset=UTF-8

###
...

What would the corresponding @RestController method for that request look like?

package com.marcobehler.springmvcarticle;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BookingController {

    @PostMapping("/transactions")
    public Transaction transaction(@RequestBody TransactionDto dto) {
        // do something with the dto..create the booking..convert it to a transaction
        Transaction transaction = null;
        return transaction;
    }
}

Let’s break it down.

public Transaction transaction(@RequestBody TransactionDto dto) {

Similar to @RequestParam or @Pathvariable, you’ll need another annotation, called @RequestBody.

@RequestBody in combination with the correct Content-Type will signal Spring that it needs to have a look at the HTTP request body and convert it to whatever Content-Type the user specified: JSON in our case.

    // do something with the dto..create the booking..convert it to a transaction
    Transaction transaction = null;
    return transaction;
}

Your method then doesn’t have to care about the raw JSON string anymore, it can simply work with the TransactionDTO, save it to the database, convert it to a Transaction object, anything you want.

That is the power of Spring MVC.

How does Spring convert data formats?

There’s only one small problem: Spring knows about the Accept and Content-Type headers, but it does not know how to convert between Java Objects and JSON. Or XML. Or YAML.

It needs an appropriate 3rd-party library to do the dirty work (also called marshalling/unmarshalling or serialization/deserialization.)

And the classes that integrate between Spring MVC and these 3rd-party libaries are called HttpMessageConverters.

What is a HttpMessageConverter?

An HttpMessageConverter is an interface with four methods (note, I simplified the interface a bit for an easier explanation, as it looks a bit more advanced in real life).

  1. canRead(MediaType) → Can this converter read (JSON|XML|YAML|etc)? The MediaType passed in here is typically the value from the Content-Type request header.

  2. canWrite(MediaType) → Can this converter write (JSON|XML|YAML|etc)? The MediaType passed in here is typically the value from the Accept request header.

  3. read(Object, InputStream, MediaType) → Read my Java object from the (JSON|XML|YAML|etc.) InputStream

  4. write(Object, OutputStream, MediaType) → Write my Java object to the OutputStream as (JSON|XML|YAML|etc.)

In short, a MessageConverter needs to know what MediaTypes it supports (think: application/json) and then needs to implement two methods to do the actual reading/writing in that data format.

Which HttpMessageConverters are there?

Luckily, you don’t need to write these message converters yourself. Spring MVC comes with a class that automatically registers a couple of default HTTPMessageConverters for you - if you have the appropriate 3rd-party libraries on the classpath.

If you don’t know about this, it’ll sound like magic. In any case, have a look at Spring’s AllEncompassingFormHttpMessageConverter (I love the name).

private static final boolean jackson2XmlPresent;

private static final boolean jackson2SmilePresent;

private static final boolean gsonPresent;

private static final boolean jsonbPresent;

static {

Let’s break this down.

Spring MVC checks if the class javax.xml.bind.Binder is present and if so, assumes you have added a needed library to your project to do JAXB conversions.

private static final boolean jackson2SmilePresent;

Spring MVC checks if two classes ..jackson..ObjectMapper and ..jackson..JsonGenerator are present and if so assumes that you have added Jackson to your project in order to do the JSON conversions.

private static final boolean gsonPresent;

Spring MVC checks if the class ..jackson..XmlMapper is present and if so assumes that you have added Jackson’s XML support to your project in order to do the XML conversions.

And so on. And a couple of lines later, Spring simply adds an HttpMessageConverter for each library it 'detected'.

public AllEncompassingFormHttpMessageConverter() {
        if (!shouldIgnoreXml) {
                try {
                        addPartConverter(new SourceHttpMessageConverter<>());
                }
                catch (Error err) {
                        // Ignore when no TransformerFactory implementation is available
                }

Side-Note: Spring MVC, Spring Boot & RestControllers

When building Spring Boot projects, you’ll automatically use Spring MVC under the hood. But Spring Boot also pulls in Jackson by default.

That is the reason why you can immediately write JSON endpoints with Spring Boot, because the correct HttpMessageConverts will be added automatically for you.

Summary: @RestControllers

Compared with the HTML flow, the JSON/XML flow is a bit simpler, as you bypass all that Model and View rendering.

Instead, your @Controllers directly return Java objects, which Spring MVC will conveniently serialize to JSON/XML or any other format that the user requested with the help of HttpMessageConverters.

You need to make sure of two things, however:

  1. Have the appropriate 3rd party libraries on the classpath.

  2. Make sure to send in the correct Accept or Content-Type headers with every request.

Fin

That was quite a ride. In the end, I hope that you have taken a couple of things away from this article:

  • Spring MVC is a good old MVC framework that lets you, rather easily, write HTML web sites or JSON/XML web services.

  • It integrates nicely with a lot of templating libraries and data conversion libraries, as well as with the rest of the Spring ecosystem, like Spring Boot.

  • It mainly allows you to focus on writing your business logic, without having to worry too much about servlet plumbing code, HTTP request/response parsing, and data conversion.

That’s it for today. Thanks for reading.

Acknowledgments

A big "thank you" goes out to Patricio "Pato" Moschcovich, who not only did the proofreading for this article but also provided invaluable feedback!

There's more where that came from

I'll send you an update when I publish new guides. Absolutely no spam, ever. Unsubscribe anytime.


Share:

Comments