(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.)
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.)
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:
-
HTML → Your web app creates HTML pages that can be viewed in a browser.
-
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.
-
(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.
Let’s have a look at a super simple HttpServlet that returns a very simple, static, HTML page.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/MyServletV1.java[role=include]
Let’s break this down.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/MyServletV1.java[role=include]
Your servlet extends Java’s HttpServlet class.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/MyServletV1.java[role=include]
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.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/MyServletV1.java[role=include]
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
.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/MyServletV1.java[role=include]
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.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/MyServletV1.java[role=include]
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:
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/TomcatApplicationLauncher.java[role=include]
Let’s break this down.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/TomcatApplicationLauncher.java[role=include]
You configure a new Tomcat server which will start on port 8080.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/TomcatApplicationLauncher.java[role=include]
This is how you register your Servlet with Tomcat. This is the first part, where you simply tell Tomcat about your servlet.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/TomcatApplicationLauncher.java[role=include]
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
).
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/TomcatApplicationLauncher.java[role=include]
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.
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?
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/MyServletV2.java[role=include]
Let’s break this down.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/MyServletV2.java[role=include]
We add another if
to our doGet method, to handle the /api/users/
calls.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/MyServletV2.java[role=include]
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!
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/MyServletV2.java[role=include]
Writing JSON to the browser means setting the correct content-type.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/MyServletV2.java[role=include]
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.
mb_ad::spring_course[]
While our servlet above works, there are quite a few problems on the horizon:
-
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. -
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. -
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 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?
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:
-
It needs to have a look at the incoming HTTP method request URI and any request parameters. E.g.:
POST /register?name=john&age33
. -
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. -
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. -
It needs to take whatever the output from your @Controller was and convert it back to
HTML/JSON/XML
.
The whole process looks like this, with a fair number of intermediary classes neglected because DispatcherServlet doesn’t do all the work itself.
+--------------------------------+ curl | | --data "[email protected]&name=marco" | | https://marcobehler.com/register | | --------------------------------------------------->| [--Spring Web MVC--] | | | | | | DispatcherServlet | | | /---------------------------------------\ | | find and run | @Controller | | Handles every request | ----------------> | UserController | | | <---------------- |---------------------------------------| | | ModelAndView | | | mapping: /* | | @PostMapping("/register") | | | | public ModelAndView register(UserDto) | <---------------- return converted HTML ------------| | \---------------------------------------/ +--------------------------------+
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.
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.
For our user registration workflow from above (POST /register?name=john&age33
), you would write the following class.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/RegistrationController.java[role=include]
Let’s break this down.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/RegistrationController.java[role=include]
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.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/RegistrationController.java[role=include]
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.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/RegistrationController.java[role=include]
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.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/RegistrationController.java[role=include]
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.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/RegistrationController.java[role=include]
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.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/RegistrationController.java[role=include]
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.
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.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/resources/templates/registration-success.html[role=include]
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.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/resources/templates/registration-success.html[role=include]
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.
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?
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.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/ThymeleafConfig.java[role=include]
Let’s break this down.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/ThymeleafConfig.java[role=include]
In the end, a ThymeleafViewResolver simply implements Spring’s ViewResolver
interface. Given a template name (remember: registration-success
), ViewResolvers can find the actual template.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/ThymeleafConfig.java[role=include]
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 |
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/ThymeleafConfig.java[role=include]
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
.
mb_ad::spring_course[]
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.
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.
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.
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.
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) {
//
}
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;
}
-
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;
}
In short, when writing HTML pages with Spring MVC you’ll have to do just a few things:
-
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.
-
Execute whatever logic you need to fill your model. You can conveniently inject the model into any controller method.
-
Let your @Controller know which HTML template you want rendered and return the template’s name as a string.
-
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.
-
(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.
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": "[email protected]"}
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
.
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:
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/BankController.java[role=include]
Let’s break it down.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/BankController.java[role=include]
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.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/BankController.java[role=include]
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
.
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.
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": "[email protected]"}
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?
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/BookingController.java[role=include]
Let’s break it down.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/BookingController.java[role=include]
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.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/BookingController.java[role=include]
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.
mb_ad::spring_course[]
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
.
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).
-
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. -
canWrite(MediaType) → Can this converter write (JSON|XML|YAML|etc)? The MediaType passed in here is typically the value from the
Accept
request header. -
read(Object, InputStream, MediaType) → Read my Java object from the (JSON|XML|YAML|etc.) InputStream
-
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.
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).
link:https://raw.githubusercontent.com/spring-projects/spring-framework/master/spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java[role=include]
Let’s break this down.
link:https://raw.githubusercontent.com/spring-projects/spring-framework/master/spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java[role=include]
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.
link:https://raw.githubusercontent.com/spring-projects/spring-framework/master/spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java[role=include]
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.
link:https://raw.githubusercontent.com/spring-projects/spring-framework/master/spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java[role=include]
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'.
link:https://raw.githubusercontent.com/spring-projects/spring-framework/master/spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java[role=include]
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.
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:
-
Have the appropriate 3rd party libraries on the classpath.
-
Make sure to send in the correct
Accept
orContent-Type
headers with every request.
If you have read this guide, you should understand by now that Spring Web MVC is part of Spring framework.
On a very high-level it allows you to turn your Spring application into a web application with the help of a DispatcherServlet that routes to @Controller classes.
These can be RestControllers (where you send XML or JSON to the client) or good old HTML Controllers where you generate HTML with frameworks like Thymeleaf, Velocity or Freemarker.
The question should really be: What is the difference between Spring Web MVC & Struts?
The short, historical answer is: Spring Web MVC started out as a competitor of Struts, which was, allegedly, perceived as poorly designed by Spring developers (see wikipedia).
The modern answer is, that while Struts 2 is certainly still being used in the odd legacy project, Spring Web MVC is the basis for everything web-related in the Spring universe. From Spring Webflow to Spring Boot’s RestControllers.
You can find the working source code for most of this article in the following GitHub repository: https://github.com/marcobehler/spring-mvc-article
Simply clone the project and run the SpringMvcArticleApplication
class to start up the web application.
In short: There is no difference, Spring Boot uses and builds on top of Spring MVC.
For a more thorough explanation, you’ll need to read my What is Spring Framework? article first.
Unless you want to use Spring MVC without it (quite rare, nowadays), the quickest way will be to create a new Spring Boot project.
-
Go to https://start.spring.io/.
-
Make sure to select
Spring Web
as a dependency for your new project.
This will let you build web/RESTful applications with Spring MVC.
Spring MVC understands basically everything that HTTP offers - with the help of third-party libraries.
That means you can throw JSON, XML, or HTTP (Multipart) Fileuploads request bodies at it, and Spring will conveniently convert that input to Java objects.
Spring MVC can write anything that you want into an HttpServletResponse - with the help of 3rd-party libraries.
Be that HTML, JSON, XML or even WebSocket response bodies. Even better, it takes your Java objects and generates those response bodies for you.
-
@Controllers
, by default, return HTML to your users with the help of a templating library, unless you add the @ResponseBody annotation to specific methods, which let you return XML/JSON as well. -
@RestController’s
source code shows that it actually is a @Controller, with the added @ResponseBody annotation. Which is equivalent to writing @Controllers which have @ResponseBody annotated on every single method.@Controller @ResponseBody public @interface RestController {
-
Therefore, @RestControllers, by default, return XML/JSON, instead of HTML.
Note
|
XML and JSON are simply the most popular data formats you’ll use in a Spring MVC application. Your @Controllers/@RestControllers could, however, return anything else, like YAML for example. You’ll only need to make sure to have the right HttpMessageConverter registered in your ApplicationContext.
|
Over the years I have personally worked with almost all templating libraries and while there is a certain push to use Thymeleaf in Spring projects, I have no strong preference. So, either go with Thymeleaf (if you don’t have experience with any other framework) or choose the one you are most comfortable with.
A relatively common mistake is to have a @Controller
returning objects that you want to get converted to JSON or XML, but you are missing the @ResponseBody annotation.
Spring will return a rather meaningless 404 Not Found
exception in that case.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/_404WithMissingResponseBodyController.java[role=include]
Fix: Add @ResponseBody
or turn your @Controller
into a @RestController
.
If the two methods have different HTTP methods, it won’t be a problem.
/* this works */
@PostMapping("/users")
public void method1() {
}
@GetMapping("/users")
publi void method(2) {
}
If, however, you map the same HTTP methods to the same path, you will get a problem.
/* this won't work */
@PostMapping("/users")
public void method1() {
}
@PostMapping("/users")
publi void method(2) {
}
On application startup, this will lead to an IllegalStateException
, hinting to your ambiguous mapping.
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'howToPassAndRetrieveRequestParametersController' method
com.marcobehler.springmvcarticle.HowToPassAndRetrieveRequestParametersController#createUser(User)
to {POST /users3}: There is already 'howToPassAndRetrieveRequestParametersController' bean method
Yes, because Spring automatically URL decodes them. A common error:
Imagine your application sends out confirmation emails whenever a new user signs up and a user signs up with a "+" sign in his email address, like [email protected].
@GetMapping("/confirm")
public void confirm(@RequestParam String email, @RequestParam String token){
// confirm user...
}
If you forgot to properly URL encode the + sign in your confirmation mail and send the string as is to your @Controller, which value will the email @RequestParam contain?
It will be "marco[space][email protected]", as Spring will replace the + with a space, which is proper RFC3986 handling.
Fix: Make sure that the URLs you feed to your application are properly encoded: marco%[email protected], as Spring will decode them automatically.
In your Spring MVC @Controller or @RestController, you can simply specify the HttpSession as a method argument, and Spring will automatically inject it (creating one if it doesn’t yet exist)
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/HttpSessionController.java[role=include]
You cannot do that with random @Components or @Services, but you are still able to @Autowire the HttpSession into them.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/HttpSessionController.java[role=include]
In your Spring MVC @Controller or @RestController, you can simply specify the HttpServletRequest as a method argument and Spring will automatically inject it (creating one if it doesn’t yet exist)
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/HttpServletRequestController.java[role=include]
You cannot do that with random @Components or @Services, but you are still able to @Autowire the HttpServletRequest into them.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/HttpServletRequestController.java[role=include]
There’s a variety of ways to access the request headers, depending on whether you want just a single one or a map with all of them. In either case you need to annotate them with @RequestHeader.
Whatever version you pick, try to be consistent with your choice.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/HttpHeaderController.java[role=include]
For reading cookies you can use the @CookieValue
annotation in your controllers. You’ll have to write cookies directly to the HttpServletResponse.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/CookieController.java[role=include]
This is a trick question. There is a method, called httpServletRequest.getRemoteAddr()
, which, however, only returns you the IP of the user or
the last proxy that sent the request, which 99,99% of the case is your Nginx or Apache.
Hence, you’ll need to parse the X-Forwarded-For
header for the correct IP address. But what happens if your application, in addition, runs behind a CDN, like CloudFront? Then your X-Forwarded-For would look like this:
X-Forwarded-For: maybeSomeSpoofedIp, realIp, cloudFrontIp
The problem is, you cannot read the header from left to right, as users could provide and therefore spoof their own X-Forwarded-For header. You always have to go from right
to left, and exclude
all known IP addresses. In the case of CloudFront, this means you’ll need to know the CloudFront IP ranges and remove them from the header. Yup!
This leads to quite elaborate IP-resolving code. Guess how many projects get that wrong!
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/IpController.java[role=include]
Given that you have the proper HTML file upload form, which reads something like this:
<form method="POST" enctype="multipart/form-data" action="/upload">
File to upload:<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>
You simply need a @Controller with a corresponding @PostMapping and a MultiPartFile parameter, which contains your upload data and convenient methods to store the file on your disk.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/FileUploadController.java[role=include]
There’s a variety of ways to get this working, from writing directly to the HttpServletResponse or returning a byte[] as a result.
However, the most Spring-y and flexible version is by returning `ResponseEntity<Resource>`s. Depending on where you stored the file, you would use a different resource.
-
On a disk → FileSystemResource
-
On your project’s classpath → ClassPathResource
-
Stream it from "somewhere" → InputStreamResource
-
Have it available as byte[] in memory → ByteArrayResource
All that’s left to do then, is set the appropriate response HTTP headers (filename, content-type, etc).
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/FileDownloadController.java[role=include]
There’s literally a gazillion ways of handling exceptions with Spring MVC, if you don’t want to handle them directly in your @Controller methods, but rather in one central place.
Create a @ControllerAdvice
or @RestControllerAdvice
class, in combination with the @ResponseStatus and @ExceptionHandler annotations. A couple of notes:
-
You might guess the difference between these two by understanding the difference between @Controllers and @RestControllers.
-
@ResponseStatus lets you define the HTTP Status code that should be returned to the client after handling your exception.
-
@ExceptionHandler specifies the exception that should trigger your handler method.
-
Other than that, it’s like writing a normal @Controller or @RestController.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/GlobalControllerExceptionHandler.java[role=include]
Throw a ResponseStatusException
, with the appropriate status code and possibly a reason.
An alternative would be returning a ResponseEntity object, but the exception is nicer in most cases.
link:https://raw.githubusercontent.com/marcobehler/spring-mvc-article/master/src/main/java/com/marcobehler/springmvcarticle/HttpStatusCodeController.java[role=include]
The official Spring MVC’s documentation literally contains hundreds of pages describing how the web framework works.
So, in case you want to know more about Models, Views, ViewHandlers, InitBinders, RootContexts, Filters, Caching, etc., I invite you to check it out. It is simply not in the scope of this guide to cover everything.
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 also offers extensive support for testing, something which I did not cover in this article. To get a deep dive into the testing topic, you can check out e.g. this course (Note: I’m affiliated with the author)
-
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.
A big "thank you" goes out to Patricio "Pato" Moschcovich, who not only did the proofreading for this article but also provided invaluable feedback!