headers
for optimal performance. See the section on configuring Static
Resources.
1.8.4.
ETag
Filter
You can use the ShallowEtagHeaderFilter
to add “shallow” eTag
values that are computed from the
response content and, thus, save bandwidth but not CPU time. See Shallow
ETag.
1.9. View Technologies
The use of view technologies in Spring MVC is pluggable, whether you decide to use Thymeleaf, Groovy Markup Templates, JSPs, or other technologies, is primarily a matter of a configuration change. This chapter covers view technologies integrated with Spring MVC. We assume you are already familiar with View Resolution.
1.9.1. Thymeleaf
Thymeleaf is a modern server-side Java template engine that emphasizes natural HTML templates that can be previewed in a browser by double-clicking, which is very helpful for independent work on UI templates (for example, by a designer) without the need for a running server. If you want to replace JSPs, Thymeleaf offers one of the most extensive set of features to make such a transition easier. Thymeleaf is actively developed and maintained. For a more complete introduction, see the Thymeleaf project home page.
The Thymeleaf integration with Spring MVC is managed by the Thymeleaf project.
The configuration involves a few bean declarations, such as
ServletContextTemplateResolver
, SpringTemplateEngine
, and ThymeleafViewResolver
.
See Thymeleaf+Spring for more
details.
1.9.2. FreeMarker
Apache FreeMarker is a template engine for generating any kind of text output from HTML to email and others. The Spring Framework has built-in integration for using Spring MVC with FreeMarker templates.
View Configuration
The following example shows how to configure FreeMarker as a view technology:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.freeMarker()
}
// Configure FreeMarker...
@Bean
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
setTemplateLoaderPath("/WEB-INF/freemarker")
}
}
The following example shows how to configure the same in XML:
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:freemarker/>
</mvc:view-resolvers>

<mvc:freemarker-configurer>
<mvc:template-loader-path location="/WEB-INF/freemarker"/>
</mvc:freemarker-configurer>
Alternatively, you can also declare the FreeMarkerConfigurer
bean for full
control over all
properties, as the following example shows:
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
</bean>
Your templates need to be stored in the directory specified by the FreeMarkerConfigurer
shown in the preceding example. Given the preceding configuration, if your controller
returns a view name of welcome
, the resolver looks for the
/WEB-INF/freemarker/welcome.ftl
template.
FreeMarker Configuration
You can pass FreeMarker 'Settings' and 'SharedVariables' directly to the FreeMarker
Configuration
object (which is managed by Spring) by setting the
appropriate bean
properties on the FreeMarkerConfigurer
bean. The
freemarkerSettings
property requires
a java.util.Properties
object, and the freemarkerVariables
property requires a
java.util.Map
. The following example shows how to use a FreeMarkerConfigurer
:
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
<property name="freemarkerVariables">
<map>
<entry key="xml_escape" value-ref="fmXmlEscape"/>
</map>
</property>
</bean>
<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>
See the FreeMarker documentation for details of settings and variables as they apply to
the Configuration
object.
Form Handling
Spring provides a tag library for use in JSPs that contains, among others, a
<spring:bind/>
element. This element primarily lets forms display
values from
form-backing objects and show the results of failed validations from a
Validator
in the
web or business tier. Spring also has support for the same functionality in FreeMarker,
with additional convenience macros for generating form input elements themselves.
The Bind Macros
A standard set of macros are maintained within the spring-webmvc.jar
file for
FreeMarker, so they are always available to a suitably configured application.
Some of the macros defined in the Spring templating libraries are considered internal
(private), but no such scoping exists in the macro definitions, making all macros
visible
to calling code and user templates. The following sections concentrate only on the
macros
you need to directly call from within your templates. If you wish to view the macro
code
directly, the file is called spring.ftl
and is in the
org.springframework.web.servlet.view.freemarker
package.
Simple Binding
In your HTML forms based on FreeMarker templates that act as a form view for a Spring
MVC
controller, you can use code similar to the next example to bind to field values and
display error messages for each input field in similar fashion to the JSP
equivalent. The
following example shows a personForm
view:

<#import "/spring.ftl" as spring/>

...
<form action="" method="POST">
Name:
<EMAIL_ADDRESS>"personForm.name"/>
<input type="text"
name="${spring.status.expression}"
value="${spring.status.value?html}"/>

<#list spring.status.errorMessages as error> ${error}
 </#list>

...
<input type="submit" value="submit"/>
</form>
...

<EMAIL_ADDRESS>requires a 'path' argument, which consists of the
name of your command
object (it is 'command', unless you changed it in your controller configuration)
followed
by a period and the name of the field on the command object to which you wish to
bind. You
can also use nested fields, such as command.address.street
. The bind
macro assumes the
default HTML escaping behavior specified by the ServletContext
parameter
defaultHtmlEscape
in web.xml
.
An alternative form of the macro called<EMAIL_ADDRESS>takes a second argument
that explicitly specifies whether HTML escaping should be used in the status error
messages or values. You can set it to true
or false
as
required. Additional form
handling macros simplify the use of HTML escaping, and you should use these macros
wherever possible. They are explained in the next section.
Input Macros
Additional convenience macros for FreeMarker simplify both binding and form generation (including validation error display). It is never necessary to use these macros to generate form input fields, and you can mix and match them with simple HTML or direct calls to the Spring bind macros that we highlighted previously.
The following table of available macros shows the FreeMarker Template (FTL) definitions and the parameter list that each takes:
| macro | FTL definition |
|---|---|
|
<EMAIL_ADDRESS>code/> |
|
<EMAIL_ADDRESS>code, text/> |
|
<EMAIL_ADDRESS>relativeUrl/> |
|
<EMAIL_ADDRESS>path, attributes, fieldType/> |
|
<EMAIL_ADDRESS>path, attributes/> |
|
<EMAIL_ADDRESS>path, attributes/> |
|
<EMAIL_ADDRESS>path, attributes/> |
|
<EMAIL_ADDRESS>path, options, attributes/> |
|
<EMAIL_ADDRESS>path, options, attributes/> |
|
<EMAIL_ADDRESS>path, options separator, attributes/> |
|
<EMAIL_ADDRESS>path, options, separator, attributes/> |
|
<EMAIL_ADDRESS>path, attributes/> |
|
<EMAIL_ADDRESS>separator, classOrStyle/> |
In FreeMarker templates, formHiddenInput and formPasswordInput
are not actually
required, as you can use the normal formInput macro, specifying
hidden or password
as the value for the fieldType parameter.
|
The parameters to any of the above macros have consistent meanings:
-
path
: The name of the field to bind to (ie "command.name") -
options
: AMap
of all the available values that can be selected from in the input field. The keys to the map represent the values that are POSTed back from the form and bound to the command object. Map objects stored against the keys are the labels displayed on the form to the user and may be different from the corresponding values posted back by the form. Usually, such a map is supplied as reference data by the controller. You can use anyMap
implementation, depending on required behavior. For strictly sorted maps, you can use aSortedMap
(such as aTreeMap
) with a suitableComparator
and, for arbitrary Maps that should return values in insertion order, use aLinkedHashMap
or aLinkedMap
fromcommons-collections
. -
separator
: Where multiple options are available as discreet elements (radio buttons or checkboxes), the sequence of characters used to separate each one in the list (such as

). -
attributes
: An additional string of arbitrary tags or text to be included within the HTML tag itself. This string is echoed literally by the macro. For example, in atextarea
field, you may supply attributes (such as 'rows="5" cols="60"'), or you could pass style information such as 'style="border:1px solid silver"'. -
classOrStyle
: For theshowErrors
macro, the name of the CSS class that thespan
element that wraps each error uses. If no information is supplied (or the value is empty), the errors are wrapped in
tags.
The following sections outline examples of the macros.
The formInput
macro takes the path
parameter (command.name
)
and an additional attributes
parameter (which is empty in the upcoming example). The macro, along with all
other form
generation macros, performs an implicit Spring bind on the path parameter. The
binding
remains valid until a new bind occurs, so the showErrors
macro does
not need to pass the
path parameter again — it operates on the field for which a binding was last
created.
The showErrors
macro takes a separator parameter (the characters
that are used to
separate multiple errors on a given field) and also accepts a second
parameter — this
time, a class name or style attribute. Note that FreeMarker can specify default
values for the attributes parameter. The following example shows how to use the
formInput
and showWErrors
macros:
<EMAIL_ADDRESS>"command.name"/>
<EMAIL_ADDRESS>"
"/>
The next example shows the output of the form fragment, generating the name field and displaying a validation error after the form was submitted with no value in the field. Validation occurs through Spring’s Validation framework.
The generated HTML resembles the following example:
Name:
<input type="text" name="name" value="">

required

The formTextarea
macro works the same way as the
formInput
macro and accepts the same
parameter list. Commonly, the second parameter (attributes
) is used
to pass style
information or rows
and cols
attributes for the textarea
.
You can use four selection field macros to generate common UI value selection inputs in your HTML forms:
-
formSingleSelect
-
formMultiSelect
-
formRadioButtons
-
formCheckboxes
Each of the four macros accepts a Map
of options that contains the
value for the form
field and the label that corresponds to that value. The value and the label can
be the
same.
The next example is for radio buttons in FTL. The form-backing object specifies a default value of 'London' for this field, so no validation is necessary. When the form is rendered, the entire list of cities to choose from is supplied as reference data in the model under the name 'cityMap'. The following listing shows the example:
...
Town:
<EMAIL_ADDRESS>"command.address.town", cityMap, ""/>

The preceding listing renders a line of radio buttons, one for each value in
cityMap
, and uses a
separator of ""
. No additional attributes are supplied (the last
parameter to the macro is
missing). The cityMap
uses the same String
for each
key-value pair in the map. The map’s
keys are what the form actually submits as POST
request parameters.
The map values are the
labels that the user sees. In the preceding example, given a list of three well
known cities
and a default value in the form backing object, the HTML resembles the
following:
Town:
<input type="radio" name="address.town" value="London">London</input>
<input type="radio" name="address.town" value="Paris" checked="checked">Paris</input>
<input type="radio" name="address.town" value="New York">New York</input>
If your application expects to handle cities by internal codes (for example), you can create the map of codes with suitable keys, as the following example shows:
protected fun referenceData(request: HttpServletRequest): Map<String, *> {
val cityMap = linkedMapOf(
"LDN" to "London",
"PRS" to "Paris",
"NYC" to "New York"
)
return hashMapOf("cityMap" to cityMap)
}
The code now produces output where the radio values are the relevant codes, but the user still sees the more user-friendly city names, as follows:
Town:
<input type="radio" name="address.town" value="LDN">London</input>
<input type="radio" name="address.town" value="PRS" checked="checked">Paris</input>
<input type="radio" name="address.town" value="NYC">New York</input>
HTML Escaping
Default usage of the form macros described earlier results in HTML elements that are
HTML 4.01
compliant and that use the default value for HTML escaping defined in your web.xml
file, as
used by Spring’s bind support. To make the elements be XHTML compliant or to
override
the default HTML escaping value, you can specify two variables in your template (or
in
your model, where they are visible to your templates). The advantage of specifying
them in the templates is that they can be changed to different values later in the
template processing to provide different behavior for different fields in your form.
To switch to XHTML compliance for your tags, specify a value of true
for
a
model or context variable named xhtmlCompliant
, as the following
example shows:
<#-- for FreeMarker -->
<#assign xhtmlCompliant = true>
After processing this directive, any elements generated by the Spring macros are now XHTML compliant.
In similar fashion, you can specify HTML escaping per field, as the following example shows:
<#-- until this point, default HTML escaping is used -->
<#assign htmlEscape = true>
<#-- next field will use HTML escaping -->
<EMAIL_ADDRESS>"command.name"/>
<#assign htmlEscape = false in spring>
<#-- all future fields will be bound with HTML escaping off -->
1.9.3. Groovy Markup
The Groovy Markup Template Engine is primarily aimed at generating XML-like markup (XML, XHTML, HTML5, and others), but you can use it to generate any text-based content. The Spring Framework has a built-in integration for using Spring MVC with Groovy Markup.
| The Groovy Markup Template engine requires Groovy 2.3.1+. |
Configuration
The following example shows how to configure the Groovy Markup Template Engine:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.groovy()
}
// Configure the Groovy Markup Template Engine...
@Bean
fun groovyMarkupConfigurer() = GroovyMarkupConfigurer().apply {
resourceLoaderPath = "/WEB-INF/"
}
}
The following example shows how to configure the same in XML:
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:groovy/>
</mvc:view-resolvers>

<mvc:groovy-configurer resource-loader-path="/WEB-INF/"/>
Example
Unlike traditional template engines, Groovy Markup relies on a DSL that uses a builder syntax. The following example shows a sample template for an HTML page:
yieldUnescaped '<!DOCTYPE html>'
html(lang:'en') {
head {
meta('http-equiv':'"Content-Type" content="text/html; charset=utf-8"')
title('My page')
}
body {
p('This is an example of HTML contents')
}
}
1.9.4. Script Views
The Spring Framework has a built-in integration for using Spring MVC with any templating library that can run on top of the JSR-223 Java scripting engine. We have tested the following templating libraries on different script engines:
| Scripting Library | Scripting Engine |
|---|---|
The basic rule for integrating any other script engine is that it must implement the
ScriptEngine and Invocable interfaces.
|
Requirements
You need to have the script engine on your classpath, the details of which vary by script engine:
-
The Nashorn JavaScript engine is provided with Java 8+. Using the latest update release available is highly recommended.
-
JRuby should be added as a dependency for Ruby support.
-
Jython should be added as a dependency for Python support.
-
org.jetbrains.kotlin:kotlin-script-util
dependency and aMETA-INF/services/javax.script.ScriptEngineFactory
file containing aorg.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory
line should be added for Kotlin script support. See this example for more details.
You need to have the script templating library. One way to do that for Javascript is through WebJars.
Script Templates
You can declare a ScriptTemplateConfigurer
bean to specify the script engine
to use,
the script files to load, what function to call to render templates, and so on.
The following example uses Mustache templates and the Nashorn JavaScript engine:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.scriptTemplate()
}
@Bean
fun configurer() = ScriptTemplateConfigurer().apply {
engineName = "nashorn"
setScripts("mustache.js")
renderObject = "Mustache"
renderFunction = "render"
}
}
The following example shows the same arrangement in XML:
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:script-template/>
</mvc:view-resolvers>
<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render">
<mvc:script location="mustache.js"/>
</mvc:script-template-configurer>
The controller would look no different for the Java and XML configurations, as the following example shows:
@Controller
class SampleController {
@GetMapping("/sample")
fun test(model: Model): String {
model["title"] = "Sample title"
model["body"] = "Sample body"
return "template"
}
}
The following example shows the Mustache template:

<title>{{title}}</title>

{{body}}

The render function is called with the following parameters:
-
String template
: The template content -
Map model
: The view model -
RenderingContext renderingContext
: TheRenderingContext
that gives access to the application context, the locale, the template loader, and the URL (since 5.0)
Mustache.render()
is natively compatible with this signature, so you can
call it directly.
If your templating technology requires some customization, you can provide a script that implements a custom render function. For example, Handlerbars needs to compile templates before using them and requires a polyfill to emulate some browser facilities that are not available in the server-side script engine.
The following example shows how to do so:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.scriptTemplate()
}
@Bean
fun configurer() = ScriptTemplateConfigurer().apply {
engineName = "nashorn"
setScripts("polyfill.js", "handlebars.js", "render.js")
renderFunction = "render"
isSharedEngine = false
}
}
Setting the sharedEngine property to false is required
when you use non-thread-safe
script engines with templating libraries not designed for concurrency, such as
Handlebars or
React running on Nashorn. In that case, Java 8u60 or greater is required, due
to this bug.
|
polyfill.js
defines only the window
object needed by Handlebars
to run properly, as follows:
var window = {};
This basic render.js
implementation compiles the template before using it. A
production-ready
implementation should also store any reused cached templates or pre-compiled templates.
You can do so on the script side (and handle any customization you need — managing
template engine configuration, for example). The following example shows how to do so:
function render(template, model) {
var compiledTemplate = Handlebars.compile(template);
return compiledTemplate(model);
}
1.9.5. JSP and JSTL
The Spring Framework has a built-in integration for using Spring MVC with JSP and JSTL.
View Resolvers
When developing with JSPs, you can declare a InternalResourceViewResolver
or
a
ResourceBundleViewResolver
bean.
ResourceBundleViewResolver
relies on a properties file to define the view
names
mapped to a class and a URL. With a ResourceBundleViewResolver
, you can mix
different types of views by using only one resolver, as the following example shows:

<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views"/>
</bean>
# And a sample properties file is used (views.properties in WEB-INF/classes):
welcome.(class)=org.springframework.web.servlet.view.JstlView
welcome.url=/WEB-INF/jsp/welcome.jsp
productList.(class)=org.springframework.web.servlet.view.JstlView
productList.url=/WEB-INF/jsp/productlist.jsp
InternalResourceViewResolver
can also be used for JSPs. As a best practice,
we strongly
encourage placing your JSP files in a directory under the 'WEB-INF'
directory so there
can be no direct access by clients.
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
JSPs versus JSTL
When using the JSP Standard Tag Library (JSTL) you must use a special view class, the
JstlView
, as JSTL needs some preparation before things such as the I18N
features can
work.
Spring’s JSP Tag Library
Spring provides data binding of request parameters to command objects, as described in earlier chapters. To facilitate the development of JSP pages in combination with those data binding features, Spring provides a few tags that make things even easier. All Spring tags have HTML escaping features to enable or disable escaping of characters.
The spring.tld
tag library descriptor (TLD) is included in the spring-webmvc.jar
.
For a comprehensive reference on individual tags, browse the
API
reference
or see the tag library description.
Spring’s form tag library
As of version 2.0, Spring provides a comprehensive set of data binding-aware tags for handling form elements when using JSP and Spring Web MVC. Each tag provides support for the set of attributes of its corresponding HTML tag counterpart, making the tags familiar and intuitive to use. The tag-generated HTML is HTML 4.01/XHTML 1.0 compliant.
Unlike other form/input tag libraries, Spring’s form tag library is integrated with Spring Web MVC, giving the tags access to the command object and reference data your controller deals with. As we show in the following examples, the form tags make JSPs easier to develop, read, and maintain.
We go through the form tags and look at an example of how each tag is used. We have included generated HTML snippets where certain tags require further commentary.
Configuration
The form tag library comes bundled in spring-webmvc.jar
. The library
descriptor is
called spring-form.tld
.
To use the tags from this library, add the following directive to the top of your JSP page:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
where form
is the tag name prefix you want to use for the tags from this
library.
The Form Tag
This tag renders an HTML 'form' element and exposes a binding path to inner tags for
binding. It puts the command object in the PageContext
so that the
command object can
be accessed by inner tags. All the other tags in this library are nested tags of the
form
tag.
Assume that we have a domain object called User
. It is a JavaBean with
properties
such as firstName
and lastName
. We can use it as the
form-backing object of our
form controller, which returns form.jsp
. The following example shows
what form.jsp
could
look like:
<form:form>

First Name:
<form:input path="firstName"/>

Last Name:
<form:input path="lastName"/>

<input type="submit" value="Save Changes"/>

</form:form>
The firstName
and lastName
values are retrieved from the
command object placed in
the PageContext
by the page controller. Keep reading to see more
complex examples of
how inner tags are used with the form
tag.
The following listing shows the generated HTML, which looks like a standard form:
<form method="POST">

First Name:
<input name="firstName" type="text" value="Harry"/>

Last Name:
<input name="lastName" type="text" value="Potter"/>

<input type="submit" value="Save Changes"/>

</form>
The preceding JSP assumes that the variable name of the form-backing object is
command
. If you have put the form-backing object into the model under
another name
(definitely a best practice), you can bind the form to the named variable, as the
following example shows:
<form:form modelAttribute="user">

First Name:
<form:input path="firstName"/>

Last Name:
<form:input path="lastName"/>

<input type="submit" value="Save Changes"/>

</form:form>
The
input
Tag
This tag renders an HTML input
element with the bound value and type='text'
by default.
For an example of this tag, see The
Form Tag. You can also use
HTML5-specific types, such as email
, tel
,
date
, and others.
The
checkbox
Tag
This tag renders an HTML input
tag with the type
set to
checkbox
.
Assume that our User
has preferences such as newsletter subscription and
a list of
hobbies. The following example shows the Preferences
class:
class Preferences(
var receiveNewsletter: Boolean,
var interests: StringArray,
var favouriteWord: String
)
The corresponding form.jsp
could then resemble the following:
<form:form>

Subscribe to newsletter?:
<%-- Approach 1: Property is of type java.lang.Boolean --%>
<form:checkbox path="preferences.receiveNewsletter"/>

Interests:
<%-- Approach 2: Property is of an array or of type java.util.Collection --%>

Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/>
Herbology: <form:checkbox path="preferences.interests" value="Herbology"/>
Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/>

Favourite Word:
<%-- Approach 3: Property is of type java.lang.Object --%>

Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/>

</form:form>
There are three approaches to the checkbox
tag, which should meet all
your checkbox needs.
-
Approach One: When the bound value is of type
java.lang.Boolean
, theinput(checkbox)
is marked aschecked
if the bound value istrue
. Thevalue
attribute corresponds to the resolved value of thesetValue(Object)
value property. -
Approach Two: When the bound value is of type
array
orjava.util.Collection
, theinput(checkbox)
is marked aschecked
if the configuredsetValue(Object)
value is present in the boundCollection
. -
Approach Three: For any other bound value type, the
input(checkbox)
is marked aschecked
if the configuredsetValue(Object)
is equal to the bound value.
Note that, regardless of the approach, the same HTML structure is generated. The following HTML snippet defines some checkboxes:

Interests:

Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/>
<input type="hidden" value="1" name="_preferences.interests"/>
Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/>
<input type="hidden" value="1" name="_preferences.interests"/>
Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/>
<input type="hidden" value="1" name="_preferences.interests"/>

You might not expect to see the additional hidden field after each checkbox.
When a checkbox in an HTML page is not checked, its value is not sent to the
server as part of the HTTP request parameters once the form is submitted, so we need
a
workaround for this quirk in HTML for Spring form data binding to work. The
checkbox
tag follows the existing Spring convention of including a
hidden parameter
prefixed by an underscore (_
) for each checkbox. By doing this, you are
effectively
telling Spring that “the checkbox was visible in the form, and I want my object to
which the form data binds to reflect the state of the checkbox, no matter what.”
The
checkboxes
Tag
This tag renders multiple HTML input
tags with the type
set
to checkbox
.
This section build on the example from the previous checkbox
tag
section. Sometimes, you prefer
not to have to list all the possible hobbies in your JSP page. You would rather
provide
a list at runtime of the available options and pass that in to the tag. That is the
purpose of the checkboxes
tag. You can pass in an Array
, a
List
, or a Map
that contains
the available options in the items
property. Typically, the bound
property is a
collection so that it can hold multiple values selected by the user. The following
example
shows a JSP that uses this tag:
<form:form>

Interests:

<%-- Property is of an array or of type java.util.Collection --%>
<form:checkboxes path="preferences.interests" items="${interestList}"/>

</form:form>
This example assumes that the interestList
is a List
available as a model attribute
that contains strings of the values to be selected from. If you use a
Map
,
the map entry key is used as the value, and the map entry’s value is used as
the label to be displayed. You can also use a custom object where you can provide
the
property names for the value by using itemValue
and the label by using
itemLabel
.
The
radiobutton
Tag
This tag renders an HTML input
element with the type
set to
radio
.
A typical usage pattern involves multiple tag instances bound to the same property but with different values, as the following example shows:

Sex:

Male: <form:radiobutton path="sex" value="M"/>

Female: <form:radiobutton path="sex" value="F"/>

The
radiobuttons
Tag
This tag renders multiple HTML input
elements with the type
set to radio
.
As with the checkboxes
tag, you might want to
pass in the available options as a runtime variable. For this usage, you can use the
radiobuttons
tag. You pass in an Array
, a
List
, or a Map
that contains the
available options in the items
property. If you use a Map
,
the map entry key is
used as the value and the map entry’s value are used as the label to be displayed.
You can also use a custom object where you can provide the property names for the
value
by using itemValue
and the label by using itemLabel
, as
the following example shows:

Sex:
<form:radiobuttons path="sex" items="${sexOptions}"/>

The
password
Tag
This tag renders an HTML input
tag with the type set to
password
with the bound value.

Password:

<form:password path="password"/>

Note that, by default, the password value is not shown. If you do want the
password value to be shown, you can set the value of the showPassword
attribute to
true
, as the following example shows:

Password:

<form:password path="password" value="^76525bvHGq" showPassword="true"/>

The
select
Tag
This tag renders an HTML 'select' element. It supports data binding to the selected
option as well as the use of nested option
and options
tags.
Assume that a User
has a list of skills. The corresponding HTML could be
as follows:

Skills:
<form:select path="skills" items="${skills}"/>

If the User’s
skill are in Herbology, the HTML source of the 'Skills'
row could be
as follows:

Skills:

<select name="skills" multiple="true">
<option value="Potions">Potions</option>
<option value="Herbology" selected="selected">Herbology</option>
<option value="Quidditch">Quidditch</option>
</select>

The
option
Tag
This tag renders an HTML option
element. It sets selected
,
based on the bound
value. The following HTML shows typical output for it:

House:

<form:select path="house">
<form:option value="Gryffindor"/>
<form:option value="Hufflepuff"/>
<form:option value="Ravenclaw"/>
<form:option value="Slytherin"/>
</form:select>

If the User’s
house was in Gryffindor, the HTML source of the 'House'
row would be
as follows:

House:

<select name="house">
<option value="Gryffindor" selected="selected">Gryffindor</option> (1)
<option value="Hufflepuff">Hufflepuff</option>
<option value="Ravenclaw">Ravenclaw</option>
<option value="Slytherin">Slytherin</option>
</select>

| 1 | Note the addition of a selected attribute. |
The
options
Tag
This tag renders a list of HTML option
elements. It sets the selected
attribute,
based on the bound value. The following HTML shows typical output for it:

Country:

<form:select path="country">
<form:option value="-" label="--Please Select"/>
<form:options items="${countryList}" itemValue="code" itemLabel="name"/>
</form:select>

If the User
lived in the UK, the HTML source of the 'Country' row would
be as follows:

Country:

<select name="country">
<option value="-">--Please Select</option>
<option value="AT">Austria</option>
<option value="UK" selected="selected">United Kingdom</option> (1)
<option value="US">United States</option>
</select>

| 1 | Note the addition of a selected attribute. |
As the preceding example shows, the combined usage of an option
tag with
the options
tag
generates the same standard HTML but lets you explicitly specify a value in the
JSP that is for display only (where it belongs), such as the default string in the
example: "-- Please Select".
The items
attribute is typically populated with a collection or array of
item objects.
itemValue
and itemLabel
refer to bean properties of those
item objects, if
specified. Otherwise, the item objects themselves are turned into strings.
Alternatively,
you can specify a Map
of items, in which case the map keys are
interpreted as option
values and the map values correspond to option labels. If itemValue
or
itemLabel
(or both)
happen to be specified as well, the item value property applies to the map key, and
the item label property applies to the map value.
The
textarea
Tag
This tag renders an HTML textarea
element. The following HTML shows
typical output for it:

Notes:
<form:textarea path="notes" rows="3" cols="20"/>
<form:errors path="notes"/>

The
hidden
Tag
This tag renders an HTML input
tag with the type
set to
hidden
with the bound value. To submit
an unbound hidden value, use the HTML input
tag with the
type
set to hidden
.
The following HTML shows typical output for it:
<form:hidden path="house"/>
If we choose to submit the house
value as a hidden one, the HTML would
be as follows:
<input name="house" type="hidden" value="Gryffindor"/>
The
errors
Tag
This tag renders field errors in an HTML span
element. It provides
access to the errors
created in your controller or those that were created by any validators associated
with
your controller.
Assume that we want to display all error messages for the firstName
and
lastName
fields once we submit the form. We have a validator for instances of the
User
class
called UserValidator
, as the following example shows:
class UserValidator : Validator {
override fun supports(candidate: Class<*>): Boolean {
return User::class.java.isAssignableFrom(candidate)
}
override fun validate(obj: Any, errors: Errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.")
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.")
}
}
The form.jsp
could be as follows:
<form:form>

First Name:
<form:input path="firstName"/>
<%-- Show errors for firstName field --%>
<form:errors path="firstName"/>

Last Name:
<form:input path="lastName"/>
<%-- Show errors for lastName field --%>
<form:errors path="lastName"/>

<input type="submit" value="Save Changes"/>

</form:form>
If we submit a form with empty values in the firstName
and lastName
fields,
the HTML would be as follows:
<form method="POST">

First Name:
<input name="firstName" type="text" value=""/>
<%-- Associated errors to firstName field displayed --%>
Field is required.

Last Name:
<input name="lastName" type="text" value=""/>
<%-- Associated errors to lastName field displayed --%>
Field is required.

<input type="submit" value="Save Changes"/>

</form>
What if we want to display the entire list of errors for a given page? The next
example
shows that the errors
tag also supports some basic wildcarding
functionality.
-
path="*"
: Displays all errors. -
path="lastName"
: Displays all errors associated with thelastName
field. -
If
path
is omitted, only object errors are displayed.
The following example displays a list of errors at the top of the page, followed by field-specific errors next to the fields:
<form:form>
<form:errors path="*" cssClass="errorBox"/>

First Name:
<form:input path="firstName"/>
<form:errors path="firstName"/>

Last Name:
<form:input path="lastName"/>
<form:errors path="lastName"/>

<input type="submit" value="Save Changes"/>

</form:form>
The HTML would be as follows:
<form method="POST">
Field is required.
Field is required.

First Name:
<input name="firstName" type="text" value=""/>
Field is required.

Last Name:
<input name="lastName" type="text" value=""/>
Field is required.

<input type="submit" value="Save Changes"/>

</form>
The spring-form.tld
tag library descriptor (TLD) is included in the
spring-webmvc.jar
.
For a comprehensive reference on individual tags, browse the
API
reference
or see the tag library description.
HTTP Method Conversion
A key principle of REST is the use of the “Uniform Interface”. This means that all
resources (URLs) can be manipulated by using the same four HTTP methods: GET, PUT,
POST,
and DELETE. For each method, the HTTP specification defines the exact semantics. For
instance, a GET should always be a safe operation, meaning that it has no side
effects,
and a PUT or DELETE should be idempotent, meaning that you can repeat these
operations
over and over again, but the end result should be the same. While HTTP defines these
four methods, HTML only supports two: GET and POST. Fortunately, there are two
possible
workarounds: you can either use JavaScript to do your PUT or DELETE, or you can do a
POST
with the “real” method as an additional parameter (modeled as a hidden input field
in an
HTML form). Spring’s HiddenHttpMethodFilter
uses this latter trick.
This
filter is a plain Servlet filter and, therefore, it can be used in combination with
any
web framework (not just Spring MVC). Add this filter to your web.xml, and a POST
with a hidden method
parameter is converted into the corresponding HTTP
method
request.
To support HTTP method conversion, the Spring MVC form tag was updated to support setting the HTTP method. For example, the following snippet comes from the Pet Clinic sample:
<form:form method="delete">

<input type="submit" value="Delete Pet"/>

</form:form>
The preceding example performs an HTTP POST, with the “real” DELETE method hidden
behind
a request parameter. It is picked up by the HiddenHttpMethodFilter
,
which is defined in
web.xml, as the following example shows:
<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpMethodFilter</filter-name>
<servlet-name>petclinic</servlet-name>
</filter-mapping>
The following example shows the corresponding @Controller
method:
@RequestMapping(method = [RequestMethod.DELETE])
fun deletePet(@PathVariable ownerId: Int, @PathVariable petId: Int): String {
clinic.deletePet(petId)
return "redirect:/owners/$ownerId"
}
HTML5 Tags
The Spring form tag library allows entering dynamic attributes, which means you can enter any HTML5 specific attributes.
The form input
tag supports entering a type attribute other than text
.
This is
intended to allow rendering new HTML5 specific input types, such as
email
, date
,
range
, and others. Note that entering type='text'
is not
required, since text
is the default type.
1.9.6. Tiles
You can integrate Tiles - just as any other view technology - in web applications that use Spring. This section describes, in a broad way, how to do so.
This section focuses on Spring’s support for Tiles version 3 in the
org.springframework.web.servlet.view.tiles3 package.
|
Dependencies
To be able to use Tiles, you have to add a dependency on Tiles version 3.0.1 or higher and its transitive dependencies to your project.
Configuration
To be able to use Tiles, you have to configure it by using files that contain definitions
(for basic information on definitions and other Tiles concepts, see
https://tiles.apache.org). In
Spring, this is done by using the TilesConfigurer
.
The following example ApplicationContext
configuration shows how to do so:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/general.xml</value>
<value>/WEB-INF/defs/widgets.xml</value>
<value>/WEB-INF/defs/administrator.xml</value>
<value>/WEB-INF/defs/customer.xml</value>
<value>/WEB-INF/defs/templates.xml</value>
</list>
</property>
</bean>
The preceding example defines five files that contain definitions. The files are all
located in
the WEB-INF/defs
directory. At initialization of the WebApplicationContext
,
the
files are loaded, and the definitions factory are initialized. After that has
been done, the Tiles included in the definition files can be used as views within your
Spring web application. To be able to use the views, you have to have a ViewResolver
as with any other view technology used with Spring. You can can use either of two
implementations, the UrlBasedViewResolver
and the ResourceBundleViewResolver
.
You can specify locale-specific Tiles definitions by adding an underscore and then the locale, as the following example shows:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/tiles.xml</value>
<value>/WEB-INF/defs/tiles_fr_FR.xml</value>
</list>
</property>
</bean>
With the preceding configuration, tiles_fr_FR.xml
is used for requests with
the fr_FR
locale,
and tiles.xml
is used by default.
| Since underscores are used to indicate locales, we recommended not using them otherwise in the file names for Tiles definitions. |
UrlBasedViewResolver
The UrlBasedViewResolver
instantiates the given viewClass
for each view it has to
resolve. The following bean defines a UrlBasedViewResolver
:
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/>
</bean>
ResourceBundleViewResolver
The ResourceBundleViewResolver
has to be provided with a property file
that contains
view names and view classes that the resolver can use. The following example shows a
bean
definition for a ResourceBundleViewResolver
and the corresponding view
names and view
classes (taken from the Pet Clinic sample):
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views"/>
</bean>
... welcomeView.(class)=org.springframework.web.servlet.view.tiles3.TilesView welcomeView.url=welcome (this is the name of a Tiles definition) vetsView.(class)=org.springframework.web.servlet.view.tiles3.TilesView vetsView.url=vetsView (again, this is the name of a Tiles definition) findOwnersForm.(class)=org.springframework.web.servlet.view.JstlView findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp ...
When you use the ResourceBundleViewResolver
, you can easily mix
different view technologies.
Note that the TilesView
class supports JSTL (the JSP Standard Tag
Library).
SimpleSpringPreparerFactory
and SpringBeanPreparerFactory
As an advanced feature, Spring also supports two special Tiles
PreparerFactory
implementations. See the Tiles documentation for details on how to use
ViewPreparer
references in your Tiles definition files.
You can specify SimpleSpringPreparerFactory
to autowire ViewPreparer
instances based on
specified preparer classes, applying Spring’s container callbacks as well as
applying
configured Spring BeanPostProcessors. If Spring’s context-wide annotation
configuration has
been activated, annotations in ViewPreparer
classes are automatically
detected and
applied. Note that this expects preparer classes in the Tiles definition files, as
the default PreparerFactory
does.
You can specify SpringBeanPreparerFactory
to operate on specified
preparer names (instead
of classes), obtaining the corresponding Spring bean from the DispatcherServlet’s
application context. The full bean creation process is in the control of the Spring
application context in this case, allowing for the use of explicit dependency
injection
configuration, scoped beans, and so on. Note that you need to define one Spring bean
definition
for each preparer name (as used in your Tiles definitions). The following example
shows
how to define a SpringBeanPreparerFactory
property on a TilesConfigurer
bean:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/general.xml</value>
<value>/WEB-INF/defs/widgets.xml</value>
<value>/WEB-INF/defs/administrator.xml</value>
<value>/WEB-INF/defs/customer.xml</value>
<value>/WEB-INF/defs/templates.xml</value>
</list>
</property>

<property name="preparerFactoryClass"
value="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory"/>
</bean>
1.9.7. RSS and Atom
Both AbstractAtomFeedView
and AbstractRssFeedView
inherit from the
AbstractFeedView
base class and are used to provide Atom and RSS Feed views,
respectively. They
are based on java.net’s ROME project and are
located in the
package org.springframework.web.servlet.view.feed
.
AbstractAtomFeedView
requires you to implement the
buildFeedEntries()
method and
optionally override the buildFeedMetadata()
method (the default implementation
is
empty). The following example shows how to do so:
class SampleContentAtomView : AbstractAtomFeedView() {
override fun buildFeedMetadata(model: Map<String, Any>,
feed: Feed, request: HttpServletRequest) {
// implementation omitted
}
override fun buildFeedEntries(model: Map<String, Any>,
request: HttpServletRequest, response: HttpServletResponse): List<Entry> {
// implementation omitted
}
}
Similar requirements apply for implementing AbstractRssFeedView
, as the
following example shows:
class SampleContentRssView : AbstractRssFeedView() {
override fun buildFeedMetadata(model: Map<String, Any>,
feed: Channel, request: HttpServletRequest) {
// implementation omitted
}
override fun buildFeedItems(model: Map<String, Any>,
request: HttpServletRequest, response: HttpServletResponse): List<Item> {
// implementation omitted
}
}
The buildFeedItems()
and buildFeedEntries()
methods pass in the
HTTP request, in case
you need to access the Locale. The HTTP response is passed in only for the setting of
cookies or other HTTP headers. The feed is automatically written to the response
object after the method returns.
For an example of creating an Atom view, see Alef Arendsen’s Spring Team Blog entry.
1.9.8. PDF and Excel
Spring offers ways to return output other than HTML, including PDF and Excel spreadsheets. This section describes how to use those features.
Introduction to Document Views
An HTML page is not always the best way for the user to view the model output, and Spring makes it simple to generate a PDF document or an Excel spreadsheet dynamically from the model data. The document is the view and is streamed from the server with the correct content type, to (hopefully) enable the client PC to run their spreadsheet or PDF viewer application in response.
In order to use Excel views, you need to add the Apache POI library to your classpath. For PDF generation, you need to add (preferably) the OpenPDF library.
| You should use the latest versions of the underlying document-generation libraries, if possible. In particular, we strongly recommend OpenPDF (for example, OpenPDF 1.2.12) instead of the outdated original iText 2.1.7, since OpenPDF is actively maintained and fixes an important vulnerability for untrusted PDF content. |
PDF Views
A simple PDF view for a word list could extend
org.springframework.web.servlet.view.document.AbstractPdfView
and implement
the
buildPdfDocument()
method, as the following example shows:
class PdfWordList : AbstractPdfView() {
override fun buildPdfDocument(model: Map<String, Any>, doc: Document, writer: PdfWriter,
request: HttpServletRequest, response: HttpServletResponse) {
val words = model["wordList"] as List<String>
for (word in words) {
doc.add(Paragraph(word))
}
}
}
A controller can return such a view either from an external view definition
(referencing it by name) or as a View
instance from the handler method.
Excel Views
Since Spring Framework 4.2,
org.springframework.web.servlet.view.document.AbstractXlsView
is provided
as a base
class for Excel views. It is based on Apache POI, with specialized subclasses (AbstractXlsxView
and AbstractXlsxStreamingView
) that supersede the outdated AbstractExcelView
class.
The programming model is similar to AbstractPdfView
, with buildExcelDocument()
as the central template method and controllers being able to return such a view from
an external definition (by name) or as a View
instance from the handler
method.
1.9.9. Jackson
Spring offers support for the Jackson JSON library.
Jackson-based JSON MVC Views
The MappingJackson2JsonView
uses the Jackson library’s
ObjectMapper
to render the response
content as JSON. By default, the entire contents of the model map (with the exception of
framework-specific classes) are encoded as JSON. For cases where the contents of the
map need to be filtered, you can specify a specific set of model attributes to encode
by using the modelKeys
property. You can also use the extractValueFromSingleKeyModel
property to have the value in single-key models extracted and serialized directly rather
than as a map of model attributes.
You can customize JSON mapping as needed by using Jackson’s provided
annotations. When you need further control, you can inject a custom
ObjectMapper
through the ObjectMapper
property, for cases where you need to provide
custom JSON
serializers and deserializers for specific types.
Jackson-based XML Views
MappingJackson2XmlView
uses the
Jackson XML
extension’s XmlMapper
to render the response content as XML. If the model contains multiple entries, you
should
explicitly set the object to be serialized by using the modelKey
bean
property. If the
model contains a single entry, it is serialized automatically.
You can customized XML mapping as needed by using JAXB or Jackson’s provided
annotations. When you need further control, you can inject a custom
XmlMapper
through the ObjectMapper
property, for cases where custom XML
you need to provide serializers and deserializers for specific types.
1.9.10. XML Marshalling
The MarshallingView
uses an XML Marshaller
(defined in the org.springframework.oxm
package) to render the response content as XML. You can explicitly set the object to be
marshalled by using a MarshallingView
instance’s modelKey
bean
property. Alternatively,
the view iterates over all model properties and marshals the first type that is supported
by the Marshaller
. For more information on the functionality in the
org.springframework.oxm
package, see Marshalling
XML using O/X Mappers.
1.9.11. XSLT Views
XSLT is a transformation language for XML and is popular as a view technology within web applications. XSLT can be a good choice as a view technology if your application naturally deals with XML or if your model can easily be converted to XML. The following section shows how to produce an XML document as model data and have it transformed with XSLT in a Spring Web MVC application.
This example is a trivial Spring application that creates a list of words in the
Controller
and adds them to the model map. The map is returned, along with the
view
name of our XSLT view. See Annotated
Controllers for details of Spring Web MVC’s
Controller
interface. The XSLT controller turns the list of words into a simple
XML
document ready for transformation.
Beans
Configuration is standard for a simple Spring web application: The MVC configuration
has to define an XsltViewResolver
bean and regular MVC annotation
configuration.
The following example shows how to do so:
@EnableWebMvc
@ComponentScan
@Configuration
class WebConfig : WebMvcConfigurer {
@Bean
fun xsltViewResolver() = XsltViewResolver().apply {
setPrefix("/WEB-INF/xsl/")
setSuffix(".xslt")
}
}
Controller
We also need a Controller that encapsulates our word-generation logic.
The controller logic is encapsulated in a @Controller
class, with the
handler method being defined as follows:
import org.springframework.ui.set
@Controller
class XsltController {
@RequestMapping("/")
fun home(model: Model): String {
val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
val root = document.createElement("wordList")
val words = listOf("Hello", "Spring", "Framework")
for (word in words) {
val wordNode = document.createElement("word")
val textNode = document.createTextNode(word)
wordNode.appendChild(textNode)
root.appendChild(wordNode)
}
model["wordList"] = root
return "home"
}
}
So far, we have only created a DOM document and added it to the Model map. Note that you
can also load an XML file as a Resource
and use it instead of a custom DOM
document.
There are software packages available that automatically 'domify' an object graph, but, within Spring, you have complete flexibility to create the DOM from your model in any way you choose. This prevents the transformation of XML playing too great a part in the structure of your model data, which is a danger when using tools to manage the DOMification process.
Transformation
Finally, the XsltViewResolver
resolves the “home” XSLT template file and
merges the
DOM document into it to generate our view. As shown in the XsltViewResolver
configuration, XSLT templates live in the war
file in the
WEB-INF/xsl
directory
and end with an xslt
file extension.
The following example shows an XSLT transform:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" omit-xml-declaration="yes"/>
<xsl:template match="/">

<title>Hello!</title>

My First Words

<xsl:apply-templates/>

</xsl:template>
<xsl:template match="word">

<xsl:value-of select="."/>

</xsl:template>
</xsl:stylesheet>
The preceding transform is rendered as the following HTML:

<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello!</title>

My First Words

Hello

Spring

Framework

1.10. MVC Config
The MVC Java configuration and the MVC XML namespace provide default configuration suitable for most applications and a configuration API to customize it.
For more advanced customizations, which are not available in the configuration API, see Advanced Java Config and Advanced XML Config.
You do not need to understand the underlying beans created by the MVC Java configuration and the MVC namespace. If you want to learn more, see Special Bean Types and Web MVC Config.
1.10.1. Enable MVC Configuration
In Java configuration, you can use the @EnableWebMvc
annotation to enable MVC
configuration, as the following example shows:
@Configuration
@EnableWebMvc
class WebConfig
In XML configuration, you can use the <mvc:annotation-driven>
element to
enable MVC
configuration, as the following example shows:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven/>
</beans>
The preceding example registers a number of Spring MVC infrastructure beans and adapts to dependencies available on the classpath (for example, payload converters for JSON, XML, and others).
1.10.2. MVC Config API
In Java configuration, you can implement the WebMvcConfigurer
interface, as the
following example shows:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
// Implement configuration methods...
}
In XML, you can check attributes and sub-elements of
<mvc:annotation-driven/>
. You can
view the Spring MVC XML schema or
use
the code completion feature of your IDE to discover what attributes and
sub-elements are available.
1.10.3. Type Conversion
By default formatters, for Number
and Date
types are installed,
including support for
the @NumberFormat
and @DateTimeFormat
annotations. Full support
for the Joda-Time
formatting library is also installed if Joda-Time is present on the classpath.
In Java configuration, you can register custom formatters and converters, as the following example shows:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun addFormatters(registry: FormatterRegistry) {
// ...
}
}
The following example shows how to achieve the same configuration in XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="org.example.MyConverter"/>
</set>
</property>
<property name="formatters">
<set>
<bean class="org.example.MyFormatter"/>
<bean class="org.example.MyAnnotationFormatterFactory"/>
</set>
</property>
<property name="formatterRegistrars">
<set>
<bean class="org.example.MyFormatterRegistrar"/>
</set>
</property>
</bean>
</beans>
See the
FormatterRegistrar SPI
and the FormattingConversionServiceFactoryBean for more information on
when to use
FormatterRegistrar implementations.
|
1.10.4. Validation
By default, if Bean
Validation is present
on the classpath (for example, Hibernate Validator), the
LocalValidatorFactoryBean
is
registered as a global Validator
for use with @Valid
and
Validated
on controller method arguments.
In Java configuration, you can customize the global Validator
instance, as the
following example shows:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun getValidator(): Validator {
// ...
}
}
The following example shows how to achieve the same configuration in XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven validator="globalValidator"/>
</beans>
Note that you can also register Validator
implementations locally, as the
following
example shows:
@Controller
class MyController {
@InitBinder
protected fun initBinder(binder: WebDataBinder) {
binder.addValidators(FooValidator())
}
}
If you need to have a LocalValidatorFactoryBean injected somewhere,
create a bean and
mark it with @Primary in order to avoid conflict with the one declared
in the MVC configuration.
|
1.10.5. Interceptors
In Java configuration, you can register interceptors to apply to incoming requests, as the following example shows:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(LocaleChangeInterceptor())
registry.addInterceptor(ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**")
registry.addInterceptor(SecurityInterceptor()).addPathPatterns("/secure/*")
}
}
The following example shows how to achieve the same configuration in XML:
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/admin/**"/>
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/secure/*"/>
<bean class="org.example.SecurityInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
1.10.6. Content Types
You can configure how Spring MVC determines the requested media types from the request
(for example, Accept
header, URL path extension, query parameter, and others).
By default, the URL path extension is checked first — with json
,
xml
, rss
, and atom
registered as known extensions (depending on classpath dependencies). The
Accept
header
is checked second.
Consider changing those defaults to Accept
header only, and, if you must use
URL-based
content type resolution, consider using the query parameter strategy over path extensions.
See
Suffix
Match and Suffix
Match and RFD for
more details.
In Java configuration, you can customize requested content type resolution, as the following example shows:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configureContentNegotiation(configurer: ContentNegotiationConfigurer) {
configurer.mediaType("json", MediaType.APPLICATION_JSON)
configurer.mediaType("xml", MediaType.APPLICATION_XML)
}
}
The following example shows how to achieve the same configuration in XML:
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="mediaTypes">
<value>
json=application/json
xml=application/xml
</value>
</property>
</bean>
1.10.7. Message Converters
You can customize HttpMessageConverter
in Java configuration by overriding
configureMessageConverters()
(to replace the default converters created by Spring MVC) or by overriding
extendMessageConverters()
(to customize the default converters or add additional converters to the default ones).
The following example adds XML and Jackson JSON converters with a customized
ObjectMapper
instead of the default ones:
@Configuration
@EnableWebMvc
class WebConfiguration : WebMvcConfigurer {
override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
val builder = Jackson2ObjectMapperBuilder()
.indentOutput(true)
.dateFormat(SimpleDateFormat("yyyy-MM-dd"))
.modulesToInstall(ParameterNamesModule())
converters.add(MappingJackson2HttpMessageConverter(builder.build()))
converters.add(MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()))
In the preceding example,
Jackson2ObjectMapperBuilder
is used to create a common configuration for both
MappingJackson2HttpMessageConverter
and
MappingJackson2XmlHttpMessageConverter
with indentation enabled, a customized
date format,
and the registration of
jackson-module-parameter-names
,
Which adds support for accessing parameter names (a feature added in Java 8).
This builder customizes Jackson’s default properties as follows:
-
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
is disabled. -
MapperFeature.DEFAULT_VIEW_INCLUSION
is disabled.
It also automatically registers the following well-known modules if they are detected on the classpath:
-
jackson-datatype-jdk7: Support for Java 7 types, such as
java.nio.file.Path
. -
jackson-datatype-joda: Support for Joda-Time types.
-
jackson-datatype-jsr310: Support for Java 8 Date and Time API types.
-
jackson-datatype-jdk8: Support for other Java 8 types, such as
Optional
.
Enabling indentation with Jackson XML support requires
woodstox-core-asl
dependency in addition to jackson-dataformat-xml
one.
|
Other interesting Jackson modules are available:
-
jackson-datatype-money: Support for
javax.money
types (unofficial module). -
jackson-datatype-hibernate: Support for Hibernate-specific types and properties (including lazy-loading aspects).
The following example shows how to achieve the same configuration in XML:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
<property name="objectMapper" ref="xmlMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
p:indentOutput="true"
p:simpleDateFormat="yyyy-MM-dd"
p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>
<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
1.10.8. View Controllers
This is a shortcut for defining a ParameterizableViewController
that immediately
forwards to a view when invoked. You can use it in static cases when there is no Java
controller
logic to execute before the view generates the response.
The following example of Java configuration forwards a request for /
to a view
called home
:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun addViewControllers(registry: ViewControllerRegistry) {
registry.addViewController("/").setViewName("home")
}
}
The following example achieves the same thing as the preceding example, but with XML, by
using the <mvc:view-controller>
element:
<mvc:view-controller path="/" view-name="home"/>
1.10.9. View Resolvers
The MVC configuration simplifies the registration of view resolvers.
The following Java configuration example configures content negotiation view
resolution by using JSP and Jackson as a default View
for JSON rendering:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.enableContentNegotiation(MappingJackson2JsonView())
registry.jsp()
}
}
The following example shows how to achieve the same configuration in XML:
<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</mvc:default-views>
</mvc:content-negotiation>
<mvc:jsp/>
</mvc:view-resolvers>
Note, however, that FreeMarker, Tiles, Groovy Markup, and script templates also require configuration of the underlying view technology.
The MVC namespace provides dedicated elements. The following example works with FreeMarker:
<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</mvc:default-views>
</mvc:content-negotiation>
<mvc:freemarker cache="false"/>
</mvc:view-resolvers>
<mvc:freemarker-configurer>
<mvc:template-loader-path location="/freemarker"/>
</mvc:freemarker-configurer>
In Java configuration, you can add the respective Configurer
bean,
as the following example shows:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.enableContentNegotiation(MappingJackson2JsonView())
registry.freeMarker().cache(false)
}
@Bean
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
setTemplateLoaderPath("/freemarker")
}
}
1.10.10. Static Resources
This option provides a convenient way to serve static resources from a list of
Resource
-based
locations.
In the next example, given a request that starts with /resources
, the relative
path is
used to find and serve static resources relative to /public
under the web
application
root or on the classpath under /static
. The resources are served with a
one-year future
expiration to ensure maximum use of the browser cache and a reduction in HTTP requests
made by the browser. The Last-Modified
header is also evaluated and, if
present, a 304
status code is returned.
The following listing shows how to do so with Java configuration:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public", "classpath:/static/")
.setCachePeriod(31556926)
}
}
The following example shows how to achieve the same configuration in XML:
<mvc:resources mapping="/resources/**"
location="/public, classpath:/static/"
cache-period="31556926" />
The resource handler also supports a chain of
ResourceResolver
implementations and
ResourceTransformer
implementations,
which you can use to create a toolchain for working with optimized resources.
You can use the VersionResourceResolver
for versioned resource URLs based on an
MD5 hash
computed from the content, a fixed application version, or other. A
ContentVersionStrategy
(MD5 hash) is a good choice — with some notable
exceptions, such as
JavaScript resources used with a module loader.
The following example shows how to use VersionResourceResolver
in Java
configuration:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public/")
.resourceChain(true)
.addResolver(VersionResourceResolver().addContentVersionStrategy("/**"))
}
}
The following example shows how to achieve the same configuration in XML:
<mvc:resources mapping="/resources/**" location="/public/">
<mvc:resource-chain resource-cache="true">
<mvc:resolvers>
<mvc:version-resolver>
<mvc:content-version-strategy patterns="/**"/>
</mvc:version-resolver>
</mvc:resolvers>
</mvc:resource-chain>
</mvc:resources>
You can then use ResourceUrlProvider
to rewrite URLs and apply the full chain of
resolvers and
transformers — for example, to insert versions. The MVC configuration provides a ResourceUrlProvider
bean so that it can be injected into others. You can also make the rewrite transparent with
the
ResourceUrlEncodingFilter
for Thymeleaf, JSPs, FreeMarker, and others with URL
tags that
rely on HttpServletResponse#encodeURL
.
Note that, when using both EncodedResourceResolver
(for example, for serving
gzipped or
brotli-encoded resources) and VersionResourceResolver
, you must register them
in this order.
That ensures content-based versions are always computed reliably, based on the unencoded
file.
WebJars are also supported through the
WebJarsResourceResolver
which is automatically registered when the
org.webjars:webjars-locator-core
library is present on the classpath. The
resolver can
re-write URLs to include the version of the jar and can also match against incoming URLs
without versions — for example, from /jquery/jquery.min.js
to
/jquery/1.2.0/jquery.min.js
.
1.10.11. Default Servlet
Spring MVC allows for mapping the DispatcherServlet
to /
(thus
overriding the mapping
of the container’s default Servlet), while still allowing static resource requests to be
handled by the container’s default Servlet. It configures a
DefaultServletHttpRequestHandler
with a URL mapping of /**
and the
lowest priority
relative to other URL mappings.
This handler forwards all requests to the default Servlet. Therefore, it must
remain last in the order of all other URL HandlerMappings
. That is the
case if you use <mvc:annotation-driven>
. Alternatively, if you set up
your
own customized HandlerMapping
instance, be sure to set its order
property to a value
lower than that of the DefaultServletHttpRequestHandler
, which is Integer.MAX_VALUE
.
The following example shows how to enable the feature by using the default setup:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
configurer.enable()
}
}
The following example shows how to achieve the same configuration in XML:
<mvc:default-servlet-handler/>
The caveat to overriding the /
Servlet mapping is that the RequestDispatcher
for the
default Servlet must be retrieved by name rather than by path. The
DefaultServletHttpRequestHandler
tries to auto-detect the default Servlet for
the container at startup time, using a list of known names for most of the major Servlet
containers (including Tomcat, Jetty, GlassFish, JBoss, Resin, WebLogic, and WebSphere).
If the default Servlet has been custom-configured with a different name, or if a
different Servlet container is being used where the default Servlet name is unknown,
then you must explicitly provide the default Servlet’s name, as the following example shows:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
configurer.enable("myCustomDefaultServlet")
}
}
The following example shows how to achieve the same configuration in XML:
<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>
1.10.12. Path Matching
You can customize options related to path matching and treatment of the URL.
For details on the individual options, see the
PathMatchConfigurer
javadoc.
The following example shows how to customize path matching in Java configuration:
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configurePathMatch(configurer: PathMatchConfigurer) {
configurer
.setUseSuffixPatternMatch(true)
.setUseTrailingSlashMatch(false)
.setUseRegisteredSuffixPatternMatch(true)
.setPathMatcher(antPathMatcher())
.setUrlPathHelper(urlPathHelper())
.addPathPrefix("/api",
HandlerTypePredicate.forAnnotation(RestController::class.java))
}
@Bean
fun urlPathHelper(): UrlPathHelper {
//...
}
@Bean
fun antPathMatcher(): PathMatcher {
//...
}
}
The following example shows how to achieve the same configuration in XML:
<mvc:annotation-driven>
<mvc:path-matching
suffix-pattern="true"
trailing-slash="false"
registered-suffixes-only="true"
path-helper="pathHelper"
path-matcher="pathMatcher"/>
</mvc:annotation-driven>
<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>
1.10.13. Advanced Java Config
@EnableWebMvc
imports DelegatingWebMvcConfiguration
, which:
-
Provides default Spring configuration for Spring MVC applications
-
Detects and delegates to
WebMvcConfigurer
implementations to customize that configuration.
For advanced mode, you can remove @EnableWebMvc
and extend directly from
DelegatingWebMvcConfiguration
instead of implementing
WebMvcConfigurer
,
as the following example shows:
@Configuration
class WebConfig : DelegatingWebMvcConfiguration() {
// ...
}
You can keep existing methods in WebConfig
, but you can now also override bean
declarations
from the base class, and you can still have any number of other
WebMvcConfigurer
implementations on
the classpath.
1.10.14. Advanced XML Config
The MVC namespace does not have an advanced mode. If you need to customize a property on
a bean that you cannot change otherwise, you can use the BeanPostProcessor
lifecycle
hook of the Spring ApplicationContext
, as the following example shows:
@Component
class MyPostProcessor : BeanPostProcessor {
override fun postProcessBeforeInitialization(bean: Any, name: String): Any {
// ...
}
}
Note that you need to declare MyPostProcessor
as a bean, either explicitly in
XML or
by letting it be detected through a <component-scan/>
declaration.
1.11. HTTP/2
Servlet 4 containers are required to support HTTP/2, and Spring Framework 5 is compatible with Servlet API 4. From a programming model perspective, there is nothing specific that applications need to do. However, there are considerations related to server configuration. For more details, see the HTTP/2 wiki page.
The Servlet API does expose one construct related to HTTP/2. You can use the
javax.servlet.http.PushBuilder
proactively push resources to clients, and it
is supported as a method
argument to @RequestMapping
methods.
2. REST Clients
This section describes options for client-side access to REST endpoints.
2.1.
RestTemplate
RestTemplate
is a synchronous client to perform HTTP requests. It is the original
Spring REST client and exposes a simple, template-method API over underlying HTTP client
libraries.
As of 5.0, the non-blocking, reactive WebClient offers a modern alternative
to the
RestTemplate , with efficient support for both
synchronous
and asynchronous, as well as
streaming scenarios. The RestTemplate will be deprecated in a future
version and will
not have major new features added going forward.
|
See REST Endpoints for details.
2.2.
WebClient
WebClient
is a non-blocking, reactive client to perform HTTP requests. It was
introduced in 5.0 and offers a modern alternative to the RestTemplate
, with
efficient
support for both synchronous and asynchronous, as well as streaming scenarios.
In contrast to RestTemplate
, WebClient
supports the following:
-
Non-blocking I/O.
-
Reactive Streams back pressure.
-
High concurrency with fewer hardware resources.
-
Functional-style, fluent API that takes advantage of Java 8 lambdas.
-
Synchronous and asynchronous interactions.
-
Streaming up to or streaming down from a server.
See WebClient for more details.
3. Testing
This section summarizes the options available in spring-test
for Spring MVC
applications.
-
Servlet API Mocks: Mock implementations of Servlet API contracts for unit testing controllers, filters, and other web components. See Servlet API mock objects for more details.
-
TestContext Framework: Support for loading Spring configuration in JUnit and TestNG tests, including efficient caching of the loaded configuration across test methods and support for loading a
WebApplicationContext
with aMockServletContext
. See TestContext Framework for more details. -
Spring MVC Test: A framework, also known as
MockMvc
, for testing annotated controllers through theDispatcherServlet
(that is, supporting annotations), complete with the Spring MVC infrastructure but without an HTTP server. See Spring MVC Test for more details. -
Client-side REST:
spring-test
provides aMockRestServiceServer
that you can use as a mock server for testing client-side code that internally uses theRestTemplate
. See Client REST Tests for more details. -
WebTestClient
: Built for testing WebFlux applications, but it can also be used for end-to-end integration testing, to any server, over an HTTP connection. It is a non-blocking, reactive client and is well suited for testing asynchronous and streaming scenarios.
4. WebSockets
This part of the reference documentation covers support for Servlet stack, WebSocket messaging that includes raw WebSocket interactions, WebSocket emulation through SockJS, and publish-subscribe messaging through STOMP as a sub-protocol over WebSocket.
4.1. Introduction to WebSocket
The WebSocket protocol, RFC 6455, provides a standardized way to establish a full-duplex, two-way communication channel between client and server over a single TCP connection. It is a different TCP protocol from HTTP but is designed to work over HTTP, using ports 80 and 443 and allowing re-use of existing firewall rules.
A WebSocket interaction begins with an HTTP request that uses the HTTP Upgrade
header
to upgrade or, in this case, to switch to the WebSocket protocol. The following example
shows such an interaction:
GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket (1)
Connection: Upgrade (2)
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
| 1 | The Upgrade header. |
| 2 | Using the Upgrade connection. |
Instead of the usual 200 status code, a server with WebSocket support returns output similar to the following:
HTTP/1.1 101 Switching Protocols (1)
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp
| 1 | Protocol switch |
After a successful handshake, the TCP socket underlying the HTTP upgrade request remains open for both the client and the server to continue to send and receive messages.
A complete introduction of how WebSockets work is beyond the scope of this document. See RFC 6455, the WebSocket chapter of HTML5, or any of the many introductions and tutorials on the Web.
Note that, if a WebSocket server is running behind a web server (e.g. nginx), you likely need to configure it to pass WebSocket upgrade requests on to the WebSocket server. Likewise, if the application runs in a cloud environment, check the instructions of the cloud provider related to WebSocket support.
4.1.1. HTTP Versus WebSocket
Even though WebSocket is designed to be HTTP-compatible and starts with an HTTP request, it is important to understand that the two protocols lead to very different architectures and application programming models.
In HTTP and REST, an application is modeled as many URLs. To interact with the application, clients access those URLs, request-response style. Servers route requests to the appropriate handler based on the HTTP URL, method, and headers.
By contrast, in WebSockets, there is usually only one URL for the initial connect. Subsequently, all application messages flow on that same TCP connection. This points to an entirely different asynchronous, event-driven, messaging architecture.
WebSocket is also a low-level transport protocol, which, unlike HTTP, does not prescribe any semantics to the content of messages. That means that there is no way to route or process a message unless the client and the server agree on message semantics.
WebSocket clients and servers can negotiate the use of a higher-level, messaging protocol
(for example, STOMP), through the Sec-WebSocket-Protocol
header on the HTTP
handshake request.
In the absence of that, they need to come up with their own conventions.
4.1.2. When to Use WebSockets
WebSockets can make a web page be dynamic and interactive. However, in many cases, a combination of Ajax and HTTP streaming or long polling can provide a simple and effective solution.
For example, news, mail, and social feeds need to update dynamically, but it may be perfectly okay to do so every few minutes. Collaboration, games, and financial apps, on the other hand, need to be much closer to real-time.
Latency alone is not a deciding factor. If the volume of messages is relatively low (for example, monitoring network failures) HTTP streaming or polling can provide an effective solution. It is the combination of low latency, high frequency, and high volume that make the best case for the use of WebSocket.
Keep in mind also that over the Internet, restrictive proxies that are outside of your
control
may preclude WebSocket interactions, either because they are not configured to pass on the
Upgrade
header or because they close long-lived connections that appear idle.
This
means that the use of WebSocket for internal applications within the firewall is a more
straightforward decision than it is for public facing applications.
4.2. WebSocket API
The Spring Framework provides a WebSocket API that you can use to write client- and server-side applications that handle WebSocket messages.
4.2.1.
WebSocketHandler
Creating a WebSocket server is as simple as implementing WebSocketHandler
or,
more
likely, extending either TextWebSocketHandler
or
BinaryWebSocketHandler
. The following
example uses TextWebSocketHandler
:
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;
public class MyHandler extends TextWebSocketHandler {
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
// ...
}
}
There is dedicated WebSocket Java configuration and XML namespace support for mapping the preceding WebSocket handler to a specific URL, as the following example shows:
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
The following example shows the XML configuration equivalent of the preceding example:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
The preceding example is for use in Spring MVC applications and should be included
in the configuration of a DispatcherServlet
.
However, Spring’s
WebSocket support does not depend on Spring MVC. It is relatively simple to
integrate a WebSocketHandler
into other HTTP-serving environments with the help
of
WebSocketHttpRequestHandler
.
When using the WebSocketHandler
API directly vs indirectly, e.g. through the
STOMP
messaging, the application must synchronize the sending of messages
since the underlying standard WebSocket session (JSR-356) does not allow concurrent
sending. One option is to wrap the WebSocketSession
with
ConcurrentWebSocketSessionDecorator
.
4.2.2. WebSocket Handshake
The easiest way to customize the initial HTTP WebSocket handshake request is through
a HandshakeInterceptor
, which exposes methods for “before” and “after” the
handshake.
You can use such an interceptor to preclude the handshake or to make any attributes
available to the WebSocketSession
. The following example uses a built-in
interceptor
to pass HTTP session attributes to the WebSocket session:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyHandler(), "/myHandler")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
}
The following example shows the XML configuration equivalent of the preceding example:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
<websocket:handshake-interceptors>
<bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
</websocket:handshake-interceptors>
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
A more advanced option is to extend the DefaultHandshakeHandler
that performs
the steps of the WebSocket handshake, including validating the client origin,
negotiating a sub-protocol, and other details. An application may also need to use this
option if it needs to configure a custom RequestUpgradeStrategy
in order to
adapt to a WebSocket server engine and version that is not yet supported
(see
Deployment
for more on this subject).
Both the Java configuration and XML namespace make it possible to configure a custom
HandshakeHandler
.
Spring provides a WebSocketHandlerDecorator base class that you can use
to decorate
a WebSocketHandler with additional behavior. Logging and exception
handling
implementations are provided and added by default when using the WebSocket Java
configuration
or XML namespace. The ExceptionWebSocketHandlerDecorator catches all
uncaught
exceptions that arise from any WebSocketHandler method and closes the
WebSocket
session with status 1011 , which indicates a server error.
|
4.2.3. Deployment
The Spring WebSocket API is easy to integrate into a Spring MVC application where
the DispatcherServlet
serves both HTTP WebSocket handshake and other
HTTP requests. It is also easy to integrate into other HTTP processing scenarios
by invoking WebSocketHttpRequestHandler
. This is convenient and easy to
understand. However, special considerations apply with regards to JSR-356 runtimes.
The Java WebSocket API (JSR-356) provides two deployment mechanisms. The first
involves a Servlet container classpath scan (a Servlet 3 feature) at startup.
The other is a registration API to use at Servlet container initialization.
Neither of these mechanism makes it possible to use a single “front controller”
for all HTTP processing — including WebSocket handshake and all other HTTP
requests — such as Spring MVC’s DispatcherServlet
.
This is a significant limitation of JSR-356 that Spring’s WebSocket support addresses with
server-specific RequestUpgradeStrategy
implementations even when running in a
JSR-356 runtime.
Such strategies currently exist for Tomcat, Jetty, GlassFish, WebLogic, WebSphere, and
Undertow (and WildFly).
| A request to overcome the preceding limitation in the Java WebSocket API has been created and can be followed at eclipse-ee4j/websocket-api#211. Tomcat, Undertow, and WebSphere provide their own API alternatives that make it possible to do this, and it is also possible with Jetty. We are hopeful that more servers will do the same. |
A secondary consideration is that Servlet containers with JSR-356 support are expected
to perform a ServletContainerInitializer
(SCI) scan that can slow down
application
startup — in some cases, dramatically. If a significant impact is observed after an
upgrade to a Servlet container version with JSR-356 support, it should
be possible to selectively enable or disable web fragments (and SCI scanning)
through the use of the <absolute-ordering />
element in
web.xml
, as the following example shows:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<absolute-ordering/>
</web-app>
You can then selectively enable web fragments by name, such as Spring’s own
SpringServletContainerInitializer
that provides support for the Servlet 3
Java initialization API. The following example shows how to do so:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<absolute-ordering>
<name>spring_web</name>
</absolute-ordering>
</web-app>
4.2.4. Server Configuration
Each underlying WebSocket engine exposes configuration properties that control runtime characteristics, such as the size of message buffer sizes, idle timeout, and others.
For Tomcat, WildFly, and GlassFish, you can add a
ServletServerContainerFactoryBean
to your
WebSocket Java config, as the following example shows:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
}
The following example shows the XML configuration equivalent of the preceding example:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<bean class="org.springframework...ServletServerContainerFactoryBean">
<property name="maxTextMessageBufferSize" value="8192"/>
<property name="maxBinaryMessageBufferSize" value="8192"/>
</bean>
</beans>
For client-side WebSocket configuration, you should use WebSocketContainerFactoryBean
(XML) or ContainerProvider.getWebSocketContainer() (Java
configuration).
|
For Jetty, you need to supply a pre-configured Jetty WebSocketServerFactory
and
plug
that into Spring’s DefaultHandshakeHandler
through your WebSocket Java config.
The following example shows how to do so:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoWebSocketHandler(),
"/echo").setHandshakeHandler(handshakeHandler());
}
@Bean
public DefaultHandshakeHandler handshakeHandler() {
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
policy.setInputBufferSize(8192);
policy.setIdleTimeout(600000);
return new DefaultHandshakeHandler(
new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
}
}
The following example shows the XML configuration equivalent of the preceding example:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/echo" handler="echoHandler"/>
<websocket:handshake-handler ref="handshakeHandler"/>
</websocket:handlers>
<bean id="handshakeHandler" class="org.springframework...DefaultHandshakeHandler">
<constructor-arg ref="upgradeStrategy"/>
</bean>
<bean id="upgradeStrategy" class="org.springframework...JettyRequestUpgradeStrategy">
<constructor-arg ref="serverFactory"/>
</bean>
<bean id="serverFactory" class="org.eclipse.jetty...WebSocketServerFactory">
<constructor-arg>
<bean class="org.eclipse.jetty...WebSocketPolicy">
<constructor-arg value="SERVER"/>
<property name="inputBufferSize" value="8092"/>
<property name="idleTimeout" value="600000"/>
</bean>
</constructor-arg>
</bean>
</beans>
4.2.5. Allowed Origins
As of Spring Framework 4.1.5, the default behavior for WebSocket and SockJS is to accept
only same-origin requests. It is also possible to allow all or a specified list of origins.
This check is mostly designed for browser clients. Nothing prevents other types
of clients from modifying the Origin
header value (see
RFC 6454: The Web Origin Concept for more
details).
The three possible behaviors are:
-
Allow only same-origin requests (default): In this mode, when SockJS is enabled, the Iframe HTTP response header
X-Frame-Options
is set toSAMEORIGIN
, and JSONP transport is disabled, since it does not allow checking the origin of a request. As a consequence, IE6 and IE7 are not supported when this mode is enabled. -
Allow a specified list of origins: Each allowed origin must start with
http://
orhttps://
. In this mode, when SockJS is enabled, IFrame transport is disabled. As a consequence, IE6 through IE9 are not supported when this mode is enabled. -
Allow all origins: To enable this mode, you should provide
*
as the allowed origin value. In this mode, all transports are available.
You can configure WebSocket and SockJS allowed origins, as the following example shows:
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("https://mydomain.com");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
The following example shows the XML configuration equivalent of the preceding example:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers allowed-origins="https://mydomain.com">
<websocket:mapping path="/myHandler" handler="myHandler" />
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
4.3. SockJS Fallback
Over the public Internet, restrictive proxies outside your control may preclude WebSocket
interactions, either because they are not configured to pass on the Upgrade
header
or
because they close long-lived connections that appear to be idle.
The solution to this problem is WebSocket emulation — that is, attempting to use WebSocket first and then falling back on HTTP-based techniques that emulate a WebSocket interaction and expose the same application-level API.
On the Servlet stack, the Spring Framework provides both server (and also client) support for the SockJS protocol.
4.3.1. Overview
The goal of SockJS is to let applications use a WebSocket API but fall back to non-WebSocket alternatives when necessary at runtime, without the need to change application code.
SockJS consists of:
-
The SockJS protocol defined in the form of executable narrated tests.
-
The SockJS JavaScript client — a client library for use in browsers.
-
SockJS server implementations, including one in the Spring Framework
spring-websocket
module. -
A SockJS Java client in the
spring-websocket
module (since version 4.1).
SockJS is designed for use in browsers. It uses a variety of techniques to support a wide range of browser versions. For the full list of SockJS transport types and browsers, see the SockJS client page. Transports fall in three general categories: WebSocket, HTTP Streaming, and HTTP Long Polling. For an overview of these categories, see this blog post.
The SockJS client begins by sending GET /info
to
obtain basic information from the server. After that, it must decide what transport
to use. If possible, WebSocket is used. If not, in most browsers,
there is at least one HTTP streaming option. If not, then HTTP (long)
polling is used.
All transport requests have the following URL structure:
https://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}
where:
-
{server-id}
is useful for routing requests in a cluster but is not used otherwise. -
{session-id}
correlates HTTP requests belonging to a SockJS session. -
{transport}
indicates the transport type (for example,websocket
,xhr-streaming
, and others).
The WebSocket transport needs only a single HTTP request to do the WebSocket handshake. All messages thereafter are exchanged on that socket.
HTTP transports require more requests. Ajax/XHR streaming, for example, relies on one long-running request for server-to-client messages and additional HTTP POST requests for client-to-server messages. Long polling is similar, except that it ends the current request after each server-to-client send.
SockJS adds minimal message framing. For example, the server sends the letter o
(“open” frame) initially, messages are sent as a["message1","message2"]
(JSON-encoded array), the letter h
(“heartbeat” frame) if no messages flow
for 25 seconds (by default), and the letter c
(“close” frame) to close the
session.
To learn more, run an example in a browser and watch the HTTP requests.
The SockJS client allows fixing the list of transports, so it is possible to
see each transport one at a time. The SockJS client also provides a debug flag,
which enables helpful messages in the browser console. On the server side, you can enable
TRACE
logging for org.springframework.web.socket
.
For even more detail, see the SockJS protocol
narrated
test.
4.3.2. Enabling SockJS
You can enable SockJS through Java configuration, as the following example shows:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").withSockJS();
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
The following example shows the XML configuration equivalent of the preceding example:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
<websocket:sockjs/>
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
The preceding example is for use in Spring MVC applications and should be included in the
configuration of a DispatcherServlet
.
However, Spring’s WebSocket
and SockJS support does not depend on Spring MVC. It is relatively simple to
integrate into other HTTP serving environments with the help of
SockJsHttpRequestHandler
.
On the browser side, applications can use the
sockjs-client
(version
1.0.x). It
emulates the W3C WebSocket API and communicates with the server to select the best
transport option, depending on the browser in which it runs. See the
sockjs-client page and the list of
transport types supported by browser. The client also provides several
configuration options — for example, to specify which transports to include.
4.3.3. IE 8 and 9
Internet Explorer 8 and 9 remain in use. They are a key reason for having SockJS. This section covers important considerations about running in those browsers.
The SockJS client supports Ajax/XHR streaming in IE 8 and 9 by using Microsoft’s
XDomainRequest
.
That works across domains but does not support sending cookies.
Cookies are often essential for Java applications.
However, since the SockJS client can be used with many server
types (not just Java ones), it needs to know whether cookies matter.
If so, the SockJS client prefers Ajax/XHR for streaming. Otherwise, it
relies on an iframe-based technique.
The first /info
request from the SockJS client is a request for
information that can influence the client’s choice of transports.
One of those details is whether the server application relies on cookies
(for example, for authentication purposes or clustering with sticky sessions).
Spring’s SockJS support includes a property called sessionCookieNeeded
.
It is enabled by default, since most Java applications rely on the JSESSIONID
cookie. If your application does not need it, you can turn off this option,
and SockJS client should then choose xdr-streaming
in IE 8 and 9.
If you do use an iframe-based transport, keep in mind
that browsers can be instructed to block the use of IFrames on a given page by
setting the HTTP response header X-Frame-Options
to DENY
,
SAMEORIGIN
, or ALLOW-FROM <origin>
. This is used to prevent
clickjacking.
|
Spring Security 3.2+ provides support for setting
See Default
Security Headers
of the Spring Security documentation for details on how to configure the
setting of the |
If your application adds the X-Frame-Options
response header (as it should!)
and relies on an iframe-based transport, you need to set the header value to
SAMEORIGIN
or ALLOW-FROM <origin>
. The Spring SockJS
support also needs to know the location of the SockJS client, because it is loaded
from the iframe. By default, the iframe is set to download the SockJS client
from a CDN location. It is a good idea to configure this option to use
a URL from the same origin as the application.
The following example shows how to do so in Java configuration:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS()
.setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js");
}
// ...
}
The XML namespace provides a similar option through the <websocket:sockjs>
element.
During initial development, do enable the SockJS client devel mode that
prevents
the browser from caching SockJS requests (like the iframe) that would otherwise
be cached. For details on how to enable it see the
SockJS client page.
|
4.3.4. Heartbeats
The SockJS protocol requires servers to send heartbeat messages to preclude proxies
from concluding that a connection is hung. The Spring SockJS configuration has a property
called heartbeatTime
that you can use to customize the frequency. By default, a
heartbeat is sent after 25 seconds, assuming no other messages were sent on that
connection. This 25-second value is in line with the following
IETF recommendation for public Internet
applications.
| When using STOMP over WebSocket and SockJS, if the STOMP client and server negotiate heartbeats to be exchanged, the SockJS heartbeats are disabled. |
The Spring SockJS support also lets you configure the TaskScheduler
to
schedule heartbeats tasks. The task scheduler is backed by a thread pool,
with default settings based on the number of available processors. Your
should consider customizing the settings according to your specific needs.
4.3.5. Client Disconnects
HTTP streaming and HTTP long polling SockJS transports require a connection to remain open longer than usual. For an overview of these techniques, see this blog post.
In Servlet containers, this is done through Servlet 3 asynchronous support that allows exiting the Servlet container thread, processing a request, and continuing to write to the response from another thread.
A specific issue is that the Servlet API does not provide notifications for a client that has gone away. See eclipse-ee4j/servlet-api#44. However, Servlet containers raise an exception on subsequent attempts to write to the response. Since Spring’s SockJS Service supports server-sent heartbeats (every 25 seconds by default), that means a client disconnect is usually detected within that time period (or earlier, if messages are sent more frequently).
As a result, network I/O failures can occur because a client has disconnected, which
can fill the log with unnecessary stack traces. Spring makes a best effort to
identify
such network failures that represent client disconnects (specific to each server)
and log
a minimal message by using the dedicated log category, DISCONNECTED_CLIENT_LOG_CATEGORY
(defined in AbstractSockJsSession ). If you need to see the stack
traces, you can set that
log category to TRACE.
|
4.3.6. SockJS and CORS
If you allow cross-origin requests (see Allowed
Origins), the SockJS protocol
uses CORS for cross-domain support in the XHR streaming and polling transports. Therefore,
CORS headers are added automatically, unless the presence of CORS headers in the response
is detected. So, if an application is already configured to provide CORS support (for
example,
through a Servlet Filter), Spring’s SockJsService
skips this part.
It is also possible to disable the addition of these CORS headers by setting the
suppressCors
property in Spring’s SockJsService.
SockJS expects the following headers and values:
-
Access-Control-Allow-Origin
: Initialized from the value of theOrigin
request header. -
Access-Control-Allow-Credentials
: Always set totrue
. -
Access-Control-Request-Headers
: Initialized from values from the equivalent request header. -
Access-Control-Allow-Methods
: The HTTP methods a transport supports (seeTransportType
enum). -
Access-Control-Max-Age
: Set to 31536000 (1 year).
For the exact implementation, see addCorsHeaders
in
AbstractSockJsService
and
the TransportType
enum in the source code.
Alternatively, if the CORS configuration allows it, consider excluding URLs with the
SockJS endpoint prefix, thus letting Spring’s SockJsService
handle it.
4.3.7.
SockJsClient
Spring provides a SockJS Java client to connect to remote SockJS endpoints without using a browser. This can be especially useful when there is a need for bidirectional communication between two servers over a public network (that is, where network proxies can preclude the use of the WebSocket protocol). A SockJS Java client is also very useful for testing purposes (for example, to simulate a large number of concurrent users).
The SockJS Java client supports the websocket
, xhr-streaming
, and
xhr-polling
transports. The remaining ones only make sense for use in a browser.
You can configure the WebSocketTransport
with:
-
StandardWebSocketClient
in a JSR-356 runtime. -
JettyWebSocketClient
by using the Jetty 9+ native WebSocket API. -
Any implementation of Spring’s
WebSocketClient
.
An XhrTransport
, by definition, supports both xhr-streaming
and
xhr-polling
, since,
from a client perspective, there is no difference other than in the URL used to connect
to the server. At present there are two implementations:
-
RestTemplateXhrTransport
uses Spring’sRestTemplate
for HTTP requests. -
JettyXhrTransport
uses Jetty’sHttpClient
for HTTP requests.
The following example shows how to create a SockJS client and connect to a SockJS endpoint:
List<Transport> transports = new ArrayList<>(2);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
transports.add(new RestTemplateXhrTransport());
SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.doHandshake(new MyWebSocketHandler(), "ws://example.com:8080/sockjs");
SockJS uses JSON formatted arrays for messages. By default, Jackson 2 is used and
needs
to be on the classpath. Alternatively, you can configure a custom implementation of
SockJsMessageCodec and configure it on the SockJsClient .
|
To use SockJsClient
to simulate a large number of concurrent users, you
need to configure the underlying HTTP client (for XHR transports) to allow a sufficient
number of connections and threads. The following example shows how to do so with Jetty:
HttpClient jettyHttpClient = new HttpClient();
jettyHttpClient.setMaxConnectionsPerDestination(1000);
jettyHttpClient.setExecutor(new QueuedThreadPool(1000));
The following example shows the server-side SockJS-related properties (see javadoc for details) that you should also consider customizing:
@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/sockjs").withSockJS()
.setStreamBytesLimit(512 * 1024) (1)
.setHttpMessageCacheSize(1000) (2)
.setDisconnectDelay(30 * 1000); (3)
}
// ...
}
| 1 | Set the streamBytesLimit property to 512KB (the default is
128KB — 128 * 1024 ).
|
| 2 | Set the httpMessageCacheSize property to 1,000 (the default is 100 ).
|
| 3 | Set the disconnectDelay property to 30 property seconds (the default is
five seconds — 5 * 1000 ).
|
4.4. STOMP
The WebSocket protocol defines two types of messages (text and binary), but their content is undefined. The protocol defines a mechanism for client and server to negotiate a sub-protocol (that is, a higher-level messaging protocol) to use on top of WebSocket to define what kind of messages each can send, what the format is, the content of each message, and so on. The use of a sub-protocol is optional but, either way, the client and the server need to agree on some protocol that defines message content.
4.4.1. Overview
STOMP (Simple Text Oriented Messaging Protocol) was originally created for scripting languages (such as Ruby, Python, and Perl) to connect to enterprise message brokers. It is designed to address a minimal subset of commonly used messaging patterns. STOMP can be used over any reliable two-way streaming network protocol, such as TCP and WebSocket. Although STOMP is a text-oriented protocol, message payloads can be either text or binary.
STOMP is a frame-based protocol whose frames are modeled on HTTP. The following listing shows the structure of a STOMP frame:
COMMAND header1:value1 header2:value2 Body^@
Clients can use the SEND
or SUBSCRIBE
commands to send or subscribe
for
messages, along with a destination
header that describes what the
message is about and who should receive it. This enables a simple
publish-subscribe mechanism that you can use to send messages through the broker
to other connected clients or to send messages to the server to request that
some work be performed.
When you use Spring’s STOMP support, the Spring WebSocket application acts
as the STOMP broker to clients. Messages are routed to @Controller
message-handling
methods or to a simple in-memory broker that keeps track of subscriptions and
broadcasts messages to subscribed users. You can also configure Spring to work
with a dedicated STOMP broker (such as RabbitMQ, ActiveMQ, and others) for the actual
broadcasting of messages. In that case, Spring maintains
TCP connections to the broker, relays messages to it, and passes messages
from it down to connected WebSocket clients. Thus, Spring web applications can
rely on unified HTTP-based security, common validation, and a familiar programming
model for message handling.
The following example shows a client subscribing to receive stock quotes, which
the server may emit periodically (for example, via a scheduled task that sends messages
through a SimpMessagingTemplate
to the broker):
SUBSCRIBE id:sub-1 destination:/topic/price.stock.* ^@
The following example shows a client that sends a trade request, which the server
can handle through an @MessageMapping
method:
SEND destination:/queue/trade content-type:application/json content-length:44 {"action":"BUY","ticker":"MMM","shares",44}^@
After the execution, the server can broadcast a trade confirmation message and details down to the client.
The meaning of a destination is intentionally left opaque in the STOMP spec. It can
be any string, and it is entirely up to STOMP servers to define the semantics and
the syntax of the destinations that they support. It is very common, however, for
destinations to be path-like strings where /topic/..
implies publish-subscribe
(one-to-many) and /queue/
implies point-to-point (one-to-one) message
exchanges.
STOMP servers can use the MESSAGE
command to broadcast messages to all
subscribers.
The following example shows a server sending a stock quote to a subscribed client:
MESSAGE message-id:nxahklf6-1 subscription:sub-1 destination:/topic/price.stock.MMM {"ticker":"MMM","price":129.45}^@
A server cannot send unsolicited messages. All messages
from a server must be in response to a specific client subscription, and the
subscription-id
header of the server message must match the id
header of the
client subscription.
The preceding overview is intended to provide the most basic understanding of the STOMP protocol. We recommended reviewing the protocol specification in full.
4.4.2. Benefits
Using STOMP as a sub-protocol lets the Spring Framework and Spring Security provide a richer programming model versus using raw WebSockets. The same point can be made about HTTP versus raw TCP and how it lets Spring MVC and other web frameworks provide rich functionality. The following is a list of benefits:
-
No need to invent a custom messaging protocol and message format.
-
STOMP clients, including a Java client in the Spring Framework, are available.
-
You can (optionally) use message brokers (such as RabbitMQ, ActiveMQ, and others) to manage subscriptions and broadcast messages.
-
Application logic can be organized in any number of
@Controller
instances and messages can be routed to them based on the STOMP destination header versus handling raw WebSocket messages with a singleWebSocketHandler
for a given connection. -
You can use Spring Security to secure messages based on STOMP destinations and message types.
4.4.3. Enable STOMP
STOMP over WebSocket support is available in the spring-messaging
and
spring-websocket
modules. Once you have those dependencies, you can expose a
STOMP
endpoints, over WebSocket with SockJS
Fallback, as the following example shows:
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS(); (1)
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app"); (2)
config.enableSimpleBroker("/topic", "/queue"); (3)
}
}
| 1 | /portfolio is the HTTP URL for the endpoint to which a WebSocket (or
SockJS)
client needs to connect for the WebSocket handshake.
|
| 2 | STOMP messages whose destination header begins with /app are routed to
@MessageMapping methods in @Controller classes.
|
| 3 | Use the built-in message broker for subscriptions and broadcasting and
route messages whose destination header begins with /topic `or `/queue
to the broker.
|
The following example shows the XML configuration equivalent of the preceding example:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker application-destination-prefix="/app">
<websocket:stomp-endpoint path="/portfolio">
<websocket:sockjs/>
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/topic, /queue"/>
</websocket:message-broker>
</beans>
For the built-in simple broker, the /topic and /queue
prefixes do not have any special
meaning. They are merely a convention to differentiate between pub-sub versus
point-to-point
messaging (that is, many subscribers versus one consumer). When you use an external
broker,
check the STOMP page of the broker to understand what kind of STOMP destinations and
prefixes it supports.
|
To connect from a browser, for SockJS, you can use the
sockjs-client
. For STOMP,
many applications have
used the jmesnil/stomp-websocket
library
(also known as stomp.js), which is feature-complete and has been used in production for
years but is no longer maintained. At present the
JSteunou/webstomp-client is the
most
actively maintained and evolving successor of that library. The following example code
is based on it:
var socket = new SockJS("/spring-websocket-portfolio/portfolio");
var stompClient = webstomp.over(socket);
stompClient.connect({}, function(frame) {
}
Alternatively, if you connect through WebSocket (without SockJS), you can use the following code:
var socket = new WebSocket("/spring-websocket-portfolio/portfolio");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
}
Note that stompClient
in the preceding example does not need to specify login
and passcode
headers. Even if it did, they would be ignored (or, rather,
overridden) on the server side. See Connecting
to a Broker
and
Authentication
for more information on authentication.
For more example code see:
-
Using WebSocket to build an interactive web application — a getting started guide.
-
Stock Portfolio — a sample application.
4.4.4. WebSocket Server
To configure the underlying WebSocket server, the information in
Server
Configuration applies. For Jetty, however you need to set
the HandshakeHandler
and WebSocketPolicy
through the StompEndpointRegistry
:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").setHandshakeHandler(handshakeHandler());
}
@Bean
public DefaultHandshakeHandler handshakeHandler() {
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
policy.setInputBufferSize(8192);
policy.setIdleTimeout(600000);
return new DefaultHandshakeHandler(
new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
}
}
4.4.5. Flow of Messages
Once a STOMP endpoint is exposed, the Spring application becomes a STOMP broker for connected clients. This section describes the flow of messages on the server side.
The spring-messaging
module contains foundational support for messaging
applications
that originated in Spring Integration and
was
later extracted and incorporated into the Spring Framework for broader use across many
Spring projects and application scenarios.
The following list briefly describes a few of the available messaging abstractions:
-
Message: Simple representation for a message, including headers and payload.
-
MessageHandler: Contract for handling a message.
-
MessageChannel: Contract for sending a message that enables loose coupling between producers and consumers.
-
SubscribableChannel:
MessageChannel
withMessageHandler
subscribers. -
ExecutorSubscribableChannel:
SubscribableChannel
that uses anExecutor
for delivering messages.
Both the Java configuration (that is, @EnableWebSocketMessageBroker
) and the XML
namespace configuration
(that is,<websocket:message-broker>
) use the preceding components to
assemble a message
workflow. The following diagram shows the components used when the simple built-in message
broker is enabled:
The preceding diagram shows three message channels:
-
clientInboundChannel
: For passing messages received from WebSocket clients. -
clientOutboundChannel
: For sending server messages to WebSocket clients. -
brokerChannel
: For sending messages to the message broker from within server-side application code.
The next diagram shows the components used when an external broker (such as RabbitMQ) is configured for managing subscriptions and broadcasting messages:
The main difference between the two preceding diagrams is the use of the “broker relay” for passing messages up to the external STOMP broker over TCP and for passing messages down from the broker to subscribed clients.
When messages are received from a WebSocket connection, they are decoded to STOMP frames,
turned into a Spring Message
representation, and sent to the
clientInboundChannel
for further processing. For example, STOMP messages whose
destination headers start with /app
may be routed to
@MessageMapping
methods in
annotated controllers, while /topic
and /queue
messages may be
routed directly
to the message broker.
An annotated @Controller
that handles a STOMP message from a client may send a
message to
the message broker through the brokerChannel
, and the broker broadcasts the
message to matching subscribers through the clientOutboundChannel
. The same
controller can also do the same in response to HTTP requests, so a client can perform an
HTTP POST, and then a @PostMapping
method can send a message to the message
broker
to broadcast to subscribed clients.
We can trace the flow through a simple example. Consider the following example, which sets up a server:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio");
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
@Controller
public class GreetingController {
@MessageMapping("/greeting") {
public String handle(String greeting) {
return "[" + getTimestamp() + ": " + greeting;
}
}
The preceding example supports the following flow:
-
The client connects to
http://localhost:8080/portfolio
and, once a WebSocket connection is established, STOMP frames begin to flow on it. -
The client sends a SUBSCRIBE frame with a destination header of
/topic/greeting
. Once received and decoded, the message is sent to theclientInboundChannel
and is then routed to the message broker, which stores the client subscription. -
The client sends a aSEND frame to
/app/greeting
. The/app
prefix helps to route it to annotated controllers. After the/app
prefix is stripped, the remaining/greeting
part of the destination is mapped to the@MessageMapping
method inGreetingController
. -
The value returned from
GreetingController
is turned into a SpringMessage
with a payload based on the return value and a default destination header of/topic/greeting
(derived from the input destination with/app