In a fundamental sense, using FreeMarker in the web application
space is no different from anywhere else; FreeMarker writes its output
to a Writer
that you pass to the
Template.process
method, and it does not care if
that Writer
prints to the console or to a file or
to the output stream of HttpServletResponse
.
FreeMarker knows nothing about servlets and Web; it just merges Java
object with template files and generates text output from them. From
here, it is up to you how to build a Web application around
this.
But, probably you want to use FreeMarker with some already existing Web application framework. Many frameworks rely on the "Model 2" architecture, where JSP pages handle presentation. If you use such a framework (for example, Apache Struts), then read on. For other frameworks please refer to the documentation of the framework.
FreeMarker supports both traditional "javax"
Servet/JSP, and Jakarta Servlet/JSP. However, you have to use a
different Java package for them:
freemarker.ext.servet
, and
freemarker.ext.jsp
for "javax", and
freemarker.ext.jakarta.servet
, and
freemarker.ext.jakarta.jsp
for Jakarta. On modern
Servlet containers you must use the Jakarta packages, while on older
ones you must use the non-Jakarte packages.
Using FreeMarker for "Model 2"
Many frameworks follow the strategy that the HTTP request is
dispatched to user-defined "action" classes that put
data into ServletContext
,
HttpSession
and
HttpServletRequest
objects as attributes, and
then the request is forwarded by the framework to a JSP page (the
view) that will generate the HTML page using the data sent with the
attributes. This is often referred as Model 2.
With these frameworks you can simply use FTL files instead of JSP files. But, since your servlet container (Web application server), unlike with JSP files, does not know out-of-the-box what to do with FTL files, a little extra configuring is needed for your Web application:
-
Copy
freemarker.jar
(from thelib
directory of the FreeMarker distribution) into theWEB-INF/lib
directory of your Web application. -
Insert the following section to the
WEB-INF/web.xml
file of your Web application (and adjust it if required):
<servlet> <servlet-name>freemarker</servlet-name> <servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class> <!-- Or freemarker.ext.jakarta.servlet.FreemarkerServlet! --> <!-- Init-param documentation: https://freemarker.apache.org/docs/api/freemarker/ext/servlet/FreemarkerServlet.html --> <!-- FreemarkerServlet settings: --> <init-param> <param-name>TemplatePath</param-name> <param-value>/</param-value> </init-param> <init-param> <param-name>NoCache</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>ResponseCharacterEncoding</param-name> <!-- Use the output_encoding setting of FreeMarker: --> <param-value>fromTemplate</param-value> </init-param> <init-param> <param-name>ExceptionOnMissingTemplate</param-name> <!-- true => HTTP 500 on missing template, instead of HTTP 404. --> <param-value>true</param-value> </init-param> <!-- FreeMarker engine settings: --> <init-param> <param-name>incompatible_improvements</param-name> <param-value>2.3.27</param-value> <!-- Recommended to set to a high value. See: https://freemarker.apache.org/docs/pgui_config_incompatible_improvements.html --> </init-param> <init-param> <param-name>template_exception_handler</param-name> <!-- Use "html_debug" during development! --> <param-value>rethrow</param-value> </init-param> <init-param> <param-name>template_update_delay</param-name> <!-- Use 0 during development! Consider what value you need otherwise. --> <param-value>30 s</param-value> </init-param> <init-param> <param-name>default_encoding</param-name> <!-- The encoding of the template files: --> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>output_encoding</param-name> <!-- The encoding of the template output; Note that you must set "ResponseCharacterEncodring" to "fromTemplate" for this to work! --> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>locale</param-name> <!-- Influences number and date/time formatting, etc. --> <param-value>en_US</param-value> </init-param> <init-param> <param-name>number_format</param-name> <param-value>0.##########</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>freemarker</servlet-name> <url-pattern>*.ftl</url-pattern> <!-- HTML and XML auto-escaped if incompatible_improvements >= 2.3.24: --> <url-pattern>*.ftlh</url-pattern> <url-pattern>*.ftlx</url-pattern> </servlet-mapping> ... <!-- Prevent the visiting of MVC Views from outside the servlet container. RequestDispatcher.forward/include should, and will still work. Removing this may open security holes! --> <security-constraint> <web-resource-collection> <web-resource-name>FreeMarker MVC Views</web-resource-name> <url-pattern>*.ftl</url-pattern> <url-pattern>*.ftlh</url-pattern> <url-pattern>*.ftlx</url-pattern> </web-resource-collection> <auth-constraint> <!-- Nobody is allowed to visit these directly. --> </auth-constraint> </security-constraint>
After this, you can use FTL files (*.ftl
)
in the same manner as JSP (*.jsp
) files. (Of
course you can choose another extension besides
ftl
; it is just the convention)
How does it work? Let's examine how JSP-s work. Many servlet
container handles JSP-s with a servlet that is mapped to the
*.jsp
request URL pattern. That servlet will
receive all requests where the request URL ends with
.jsp
, find the JSP file based on the request
URL, and internally compiles it to a Servlet
,
and then call the generated servlet to generate the page. The
FreemarkerServlet
mapped here to the
*.ftl
URL pattern does the same, except that
FTL files are not compiled to Servlet
-s, but to
Template
objects, and then the
process
method of Template
will be called to generate the page.
For example, instead of this JSP file (note that it heavily uses Struts tag-libs to save designers from embedded Java monsters):
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <html> <head><title>Acmee Products International</title> <body> <h1>Hello <bean:write name="user"/>!</h1> <p>These are our latest offers: <ul> <logic:iterate name="latestProducts" id="prod"> <li><bean:write name="prod" property="name"/> for <bean:write name="prod" property="price"/> Credits. </logic:iterate> </ul> </body> </html>
you can use this FTL file (use ftl
file
extension instead of jsp
):
<html> <head><title>Acmee Products International</title> <body> <h1>Hello ${user}!</h1> <p>These are our latest offers: <ul> <#list latestProducts as prod> <li>${prod.name} for ${prod.price} Credits. </#list> </ul> </body> </html>
In FreeMarker <html:form
action="/query">...</html:form>
is just static text, so it is printed to the output as is, like
any other XML or HTML markup. JSP tags are just FreeMarker
directives, nothing special, so you use FreeMarker
syntax for calling them, not JSP syntax:
<@html.form
action="/query">...</@html.form>
.
Note that in the FreeMarker syntax you don't use
${...}
in
parameters as in JSP, and you don't quote the
parameter values. So this is
WRONG:
<#-- WRONG: --> <@my.jspTag color="${aVariable}" name="aStringLiteral" width="100" height=${a+b} />
and this is good:
<#-- Good: --> <@my.jspTag color=aVariable name="aStringLiteral" width=100 height=a+b />
In both templates, when you refer to user
and latestProduct
, it will first try to find a
variable with that name that was created in the template (like
prod
; if you master JSP: a page scope attribute).
If that fails, it will try to look up an attribute with that name in
the HttpServletRequest
, and if it is not there
then in the HttpSession
, and if it still doesn't
find it then in the ServletContext
. In the case
of FTL this works because FreemarkerServlet
builds the data-model from the attributes of the mentioned 3
objects. That is, in this case the root hash is not a
java.util.Map
(as it was in some example codes in
this manual), but
ServletContext
+HttpSession
+HttpServletRequest
;
FreeMarker is pretty flexible about what the data-model is. So if
you want to put variable "name"
into the
data-model, then you call
servletRequest.setAttribute("name", "Fred")
; this
is the logic of Model 2, and FreeMarker adapts itself to it.
FreemarkerServlet
also puts 3 hashes into
the data-model, by which you can access the attributes of the 3
objects directly. The hash variables are:
Request
, Session
,
Application
(corresponds to
ServletContext
). It also exposes another hash
named RequestParameters
that provides access to
the parameters of the HTTP request.
FreemarkerServlet
has various init-params.
It can be set up to load templates from an arbitrary directory, from
the classpath, or relative to the Web application directory. You can
set the charset used for templates, the default locale used by
templates, what object wrapper do you want to use, etc.
FreemarkerServlet
is easily tailored to
special needs through subclassing. Say, if you need to have
additional variables available in your data-model for all templates,
subclass the servlet and override the
preTemplateProcess()
method to shove any
additional data you need into the model before the template gets
processed. Or subclass the servlet, and set these globally available
variables as shared
variables in the Configuration
.
For more information please read the Java API documentation of the class.
Including content from other web application resources
You can use the <@include_page
path="..."/>
custom directive provided by the
FreemarkerServlet
(since 2.3.15) to include the
contents of another web application resource into the output; this
is often useful to integrate output of JSP pages (living alongside
the FreeMarker templates in the same web server) into the FreeMarker
template output. Using:
<@include_page path="path/to/some.jsp"/>
is identical to using this tag in JSP:
<jsp:include page="path/to/some.jsp">
<@include_page ...>
is not to be
confused with <#include ...>
, as the last
is for including FreeMarker templates without involving the
Servlet container. An <#include ...>
-ed
template shares the template processing state with the including
template, such as the data-model and the template-language
variables, while <@include_page ...>
starts an independent HTTP request processing.
Some Web Application Frameworks provide their own solution
for this, in which case you possibly should use that instead. Also
some Web Application Frameworks don't use
FreemarkerServlet
, so
include_page
is not available.
The path can be relative or absolute. Relative paths are
interpreted relative to the URL of the current HTTP request (one
that triggered the template processing), while absolute paths are
absolute in the current servlet context (current web application).
You can not include pages from outside the current web application.
Note that you can include any page, not just a JSP page; we just
used page with path ending in .jsp
as an
illustration.
In addition to the path
parameter, you can
also specify an optional parameter named
inherit_params
with a boolean value (defaults to
true when not specified) that specifies whether the included page
will see the HTTP request parameters of the current request or
not.
Finally, you can specify an optional parameter named
params
that specifies new request parameters that
the included page will see. In case inherited parameters are passed
too, the values of specified parameters will get prepended to the
values of inherited parameters of the same name. The value of
params
must be a hash, with each value in it
being either a string, or a sequence of strings (if you need
multivalued parameters). Here's a full example:
<@include_page path="path/to/some.jsp" inherit_params=true params={"foo": "99", "bar": ["a", "b"]}/>
This will include the page
path/to/some.jsp
, pass it all request parameters
of the current request, except for "foo" and "bar", which will be
set to "99" and multi-value of "a", "b", respectively. In case the
original request already had values for these parameters, the new
values will be prepended to the existing values. I.e. if "foo" had
values "111" and "123", then it will now have values "99", "111",
"123".
It is in fact possible to pass
non-string values for parameter values within
params
. Such a value will be converted to a
suitable Java object first (i.e. a Number, a Boolean, a Date, etc.),
and then its Java toString()
method will be used
to obtain the string value. It is better to not rely on this
mechanism, though, and instead explicitly ensure that parameter
values that aren't strings are converted to strings on the template
level where you have control over formatting using the
?string
and ?c
built-ins.
Using JSP custom tags in FTL
FreemarkerServlet
puts the
JspTaglibs
hash into the data-model, which you
can use to access JSP taglibs. The JSP custom tags will be
accessible as plain user-defined directives, and the custom EL
functions (since FreeMarker 2.3.22) as methods. For example, for
this JSP file:
<%@ page contentType="text/html;charset=ISO-8859-2" language="java"%> <%@ taglib prefix="e" uri="/WEB-INF/example.tld" %> <%@ taglib prefix="oe" uri="/WEB-INF/other-example.tld" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%-- Custom JSP tags and functions: --%> <e:someTag numParam="123" boolParam="true" strParam="Example" anotherParam="${someVar}"> ... </e:someTag> <oe:otherTag /> ${e:someELFunction(1, 2)} <%-- JSTL: --%> <c:if test="${foo}"> Do this </c:if> <c:choose> <c:when test="${x == 1}"> Do this </c:when> <c:otherwise> Do that </c:otherwise> </c:choose> <c:forEach var="person" items="${persons}"> ${person.name} </c:forEach> ${fn:trim(bar)}
the about equivalent FTL is:
<#assign e=JspTaglibs["/WEB-INF/example.tld"]> <#assign oe=JspTaglibs["/WEB-INF/other-example.tld"]> <#-- Custom JSP tags and functions: --#> <@e.someTag numParam=123 boolParam=true strParam="Example" anotherParam=someVar> ... </@e.someTag> <@oe.otherTag /> ${e.someELFunction(1, 2)} <#-- JSTL - Instead, use native FTL constructs: --> <#if foo> Do this </#if> <#if x == 1> Do this <#else> Do that </#if> <#list persons as person> ${person.name} </#list> ${bar?trim}
Parameter values don't use quotation and
"${...}"
like in
JSP. See more explanation later.
JspTaglibs
is not a core FreeMarker
feature; it only exists when the template is called through the
FreemarkerServlet
. That's because JSP
tags/functions assume a servlet environment (FreeMarker doesn't),
plus some Servlet concepts have to be emulated in the special
FreeMarker data-model that FreemarkerServlet
builds. Many modern frameworks use FreeMarker on a pure way, not
through FreemarkerServlet
.
Since JSP custom tags are written to operate in JSP
environment, they assume that variables (often referred as
"beans" in JSP world) are stored in 4 scopes: page
scope, request scope, session scope and application scope. FTL has
no such notation (the 4 scopes), but
FreemarkerServlet
provides emulated JSP
environment for the custom JSP tags, which maintains correspondence
between the "beans" of JSP scopes and FTL variables.
For the custom JSP tags, the request, session and application scopes
are exactly the same as with real JSP: the attributes of the
javax.servlet.ServletContext
,
HttpSession
and ServletRequest
objects. From the FTL side you see these 3 scopes together as the
data-model, as it was explained earlier. The page scope corresponds
to the FTL global variables (see the global
directive). That is, if you create a variable with the
global
directive, it will be visible for the
custom tags as page scope variable through the emulated JSP
environment. Also, if a JSP-tag creates a new page scope variable,
the result will be the same as if you create a variable with the
global
directive. Note that the variables in the
data-model are not visible as page-scope attributes for the JSP
tags, despite that they are globally visible, since the data-model
corresponds to the request, session and application scopes, not the
page-scope.
On JSP pages you quote all attribute values, it does not mater
if the type of the parameter is string or boolean or number. But
since custom tags are accessible in FTL templates as user-defined
FTL directives, you have to use the FTL syntax rules inside the
custom tags, not the JSP rules. So when you specify the value of an
"attribute", then on the right side of the
=
there is an FTL expression. Thus,
you must not quote boolean and numerical parameter
values (e.g. <@tiles.insert
page="/layout.ftl" flush=true/>
), or they are
interpreted as string values, and this will cause a type mismatch
error when FreeMarker tries to pass the value to the custom tag that
expects non-string value. Also note that, naturally, you can use any
FTL expression as attribute value, such as variables, calculated
values, etc. (e.g. <@tiles.insert page=layoutName
flush=foo && bar/>
).
FreeMarker does not rely on the JSP support of the servlet
container in which it is run when it uses JSP taglibs since it
implements its own lightweight JSP runtime environment. There is
only one small detail to pay attention to: to enable the FreeMarker
JSP runtime environment to dispatch events to JSP taglibs that
register event listeners in their TLD files, you should add this to
the WEB-INF/web.xml
of your Web
application:
<listener> <listener-class>freemarker.ext.jsp.EventForwarding</listener-class> <!-- Or freemarker.ext.jakarta.jsp.EventForwarding! --> </listener>
Note that you can use JSP taglibs with FreeMarker even if the
servlet container has no native JSP support, just make sure that the
javax.servlet.jsp.*
packages for JSP 2.0 (or
later) are available to your Web application.
As of this writing, JSP features up to JSP 2.1 are implemented, except the "tag files" feature of JSP 2 (i.e., custom JSP tags implemented in JSP language). The tag files had to be compiled to Java classes to be usable under FreeMarker.
JspTaglibs[uri]
will have to find the TLD for the URI specified, just like JSP's
@taglib
directive has to. For this, it implements
the TLD discovery mechanism described in the JSP specification. See
more there, but in a nutshell, it searches TLD-s in
WEB-INF/web.xml
taglib
elements, at WEB-INF/**/*.tld
, and in
WEB-INF/lib/*.{jar,zip}/META-INF/**/*.tld
.
Additionally, it can discover TLD-s that are visible for the class
loader even if they are outside the WAR structure, when you set that
up with the MetaInfTldSources
and/or
ClasspathTlds
FreemarkerServlet
init-params (since 2.3.22). See
the Java API documentation of FreemarkerServlet
for the description of these. It's also possible to set these from
Java system properties, which can be handy when you want to change
these in the Eclipse run configuration without modifying the
web.xml
; again, see the
FreemarkerServlet
API docs.
FreemarkerServlet
also recognizes the
org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern
servlet context attribute, and adds the entries from it to
MetaInfTldSources
.
Embed FTL into JSP pages
There is a taglib that allows you to put FTL fragments into JSP pages. The embedded FTL fragment can access the attributes (Beans) of the 4 JSP scopes. You can find a working example and the taglib in the FreeMarker distribution.