How to Internationalize Basic JSP/Servlet Web Applications (i18n)

Let’s explore the possibilities of internationalizing a basic JSP/Servlet web app and how to use JSTL Taglib to localize a JSP page.

First things first, we will learn how to use JSTL Taglib to localize a JSP page. Furthermore, we will demonstrate the use of request parameter, session attribute, and the cookie value to choose a preferred language in a JSP page. Besides, we will use Maven to set up the project. Hence, we expect the reader has a basic knowledge of Java Web Application, JSP, JSTL, and Maven. You can find the full implementation of this tutorial on Phrase’s GitHub.

1. Project Setup

For this simple JSP/Servlet Web Application, we only need to add jstl and javax.servlet-api dependencies into our pom.xml file:

<properties>
    <servlet.version>3.1.0</servlet.version>
    <jstl.version>1.2</jstl.version>
</properties>
<dependencies>
    <dependency>
        <groupId>jstl</groupId>
        <artifactId>jstl</artifactId>
        <version>${jstl.version}</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>${servlet.version}</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

We can find the latest version of those dependencies on Maven Central: jstl, javax.servlet-api

2. Localize A JSP Page

The basic approach to localize a JSP page is using JSTL in combination with resource bundles. Resource bundles are properties files which contain key-value pairs. Each value is a message which we want to show on the page. Whereas the key will be used to refer to that value on our JSP page. Thus, for each supported language, we will have one specific properties file.
Now, let’s create a default messages.properties file under src/main/resources as below:

label.welcome = Welcome

Next, we create a welcome.jsp page and put under /webapp folder:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page isELIgnored="false" %>
<fmt:setBundle basename="messages"/>
<html>
<head>
    <title>PhraseApp - i18n</title>
</head>
<body>
    <h2>
        <fmt:message key="label.welcome" />
    </h2>
</body>
</html>

In this page, we use <fmt:setBundle> tag to set the resource bundle
Because our resource bundle file is messages.properties, thus, we set the value of basename attribute as messages. Moreover, we use <ftm:message> tag to refer to the defined key in our properties file.
Deploy the project on an Application Server (Tomcat 8.5 at port 8080 in this case) and access welcome.jsp page, we will see below result:
Project on application server in English | Phrase
Since we haven’t mentioned which locale we want to use for the page, the message in default messages.properties file is used.
Now, we try to support the French language by adding another resource bundle file which is messages_fr.properties as below:

label.welcome = Bienvenue

Then, we modified the welcome.jsp page to use this new language:

.....
<fmt:setLocale value="fr"/>
<fmt:setBundle basename="messages" />
.....

Here, by using <fmt:setLocale> tag to set the preferred locale to fr, the new messages_fr.properties file will be used when we load the page. Consequently, we will see below result:
Project on application server in French | Phrase
Until here, we can notice that the name of resource bundle properties file will have below format:

[basename]_[locale].properties

Thereupon, we can support other languages, for example, Chinese and German by adding messages_zh.properties and messages_de.properties.
It’s worth to note that if we set the locale to a non-existing resource bundle file, the application will use the messages.properties file as default for displaying.

3. Choosing Locale Dynamically

We have explored how to set a preferred locale for a JSP page. It would be useful if we know how to do this dynamically. In this section, we will see how to set a page locale by using request parameter, session attribute, and browser cookie.

3.1. Using Request Parameter As Locale

Let’s create the requestLocale.jsp as below:

<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page isELIgnored="false"%>
<fmt:setLocale value="${param.lang}" />
<fmt:setBundle basename="messages" />
<html lang="${param.lang}">
<head>
<title>PhraseApp - i18n</title>
</head>
<body>
	<h2>
            <fmt:message key="label.chooseRequestLocale" />
        </h2>
	<p>
            <fmt:message key="label.requestLocaleContent" />
        </p>
	<p>
            <fmt:message key="label.changeLang" />
        </p>
	<ul>
		<li><a href="?lang=en"><fmt:message key="label.lang.en" /></a></li>
		<li><a href="?lang=de"><fmt:message key="label.lang.de" /></a></li>
		<li><a href="?lang=fr"><fmt:message key="label.lang.fr" /></a></li>
		<li><a href="?lang=zh"><fmt:message key="label.lang.cn" /></a></li>
	</ul>
</body>
</html>

In this page, we set the locale to the value of request parameter lang. Thus, when accessing the page which the parameter lang=zh, we will see below result:

Project on application server in Chinese | Phrase

3.2. Using Session Attribute As Locale

Likewise, to set the locale by using a session attribute, we can implement sessionLocale.jsp as below:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page isELIgnored="false" %>
<%@ page session="true" %>
<fmt:setLocale value="${sessionScope.lang}"/>
<fmt:setBundle basename="messages"/>
<html lang="${sessionScope.lang}">
<head>
    <title>PhraseApp - i18n</title>
</head>
<body>
	<h2>
	    <fmt:message key="label.welcome" />
	</h2>
	<p>
            <fmt:message key="label.sessionLocaleContent" />
        </p>
</body>
</html>

In this example, we refer to attribute lang in sessionScope as preferred locale.
Therefore, let’s create a SessionLocaleFilter to allow us updating the session attribute lang:

@WebFilter(filterName = "SessionLocaleFilter", urlPatterns = {"/*"})
public class SessionLocaleFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        if (req.getParameter("sessionLocale") != null) {
            req.getSession().setAttribute("lang", req.getParameter("sessionLocale"));
        }
        chain.doFilter(request, response);
    }
    public void destroy() {}
    public void init(FilterConfig arg0) throws ServletException {}
}

This filter will check if the request parameter sessionLocale is available for the incoming request. If yes, the filter will update the attribute lang of the current session to that value.
Now, let’s create a changeLocale.jsp page to help us update this session attribute:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page isELIgnored="false" %>
<fmt:setLocale value="${param.lang}"/>
<fmt:setBundle basename="messages"/>
<html lang="${param.lang}">
<head>
    <title>PhraseApp - i18n</title>
</head>
<body>
	<h2>
		<fmt:message key="label.chooseSessionLocale" />
	</h2>
	<ul>
		<li><a href="?sessionLocale=en"><fmt:message key="label.lang.en" /></a></li>
		<li><a href="?sessionLocale=de"><fmt:message key="label.lang.de" /></a></li>
		<li><a href="?sessionLocale=fr"><fmt:message key="label.lang.fr" /></a></li>
		<li><a href="?sessionLocale=zh"><fmt:message key="label.lang.cn" /></a></li>
	</ul>
	<c:if test="${not empty param.sessionLocale}">
		<fmt:message key="label.cookieChangeSuccess" />
		<button><a href="sessionLocale.jsp"><fmt:message key="label.viewPage" /></a></button>
	</c:if>
</body>
</html>

Then, we can run the application to access this changeLocale.jsp page and choose the German language. After that, we open our sessionLocale.jsp to see the result as below:
Project on application server in German| Phrase

3.3. Using Browser Cookie As Locale

Accordingly, using the same method, we can implement a page which uses the value of a browser cookie as locale. Thus, we have below cookieLocale.jsp page:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page isELIgnored="false" %>
<%@ page session="true" %>
<fmt:setLocale value="${cookie['lang'].value}"/>
<fmt:setBundle basename="messages"/>
<html lang="${cookie['lang'].value}">
<head>
    <title>PhraseApp - i18n</title>
</head>
<body>
	<h2>
	    <fmt:message key="label.welcome" />
	</h2>
	<p>
            <fmt:message key="label.cookieLocaleContent" />
        </p>
</body>
</html>

Suppose that we store the locale in the cookie by the key lang. Hence, we access this cookie value by using the expression cookie[‘lang’].value. Thus, our page will use that value as preferred locale.
Furthermore, we need to create a CookieLocaleFilter which helps us changing browser cookie value before we can test our page:

@WebFilter(filterName = "CookieLocaleFilter", urlPatterns = { "/*" })
public class CookieLocaleFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        if (req.getParameter("cookieLocale") != null) {
            Cookie cookie = new Cookie("lang", req.getParameter("cookieLocale"));
            res.addCookie(cookie);
        }
        chain.doFilter(request, response);
    }
    public void destroy() {}
    public void init(FilterConfig arg0) throws ServletException {}
}

For each incoming request, the above filter will check if a parameter cookieLocale is available. If yes, its value will be set to browser cookie with the key lang.
Again, let’s update the changeLocale.jsp to allow us changing this cookie value:

...
        <h2>
		<fmt:message key="label.chooseCookieLocale" />
	</h2>
	<ul>
		<li><a href="?cookieLocale=en"><fmt:message key="label.lang.en" /></a></li>
		<li><a href="?cookieLocale=de"><fmt:message key="label.lang.de" /></a></li>
		<li><a href="?cookieLocale=fr"><fmt:message key="label.lang.fr" /></a></li>
		<li><a href="?cookieLocale=zh"><fmt:message key="label.lang.cn" /></a></li>
	</ul>
	<c:if test="${not empty param.cookieLocale}">
		<fmt:message key="label.cookieChangeSuccess" />
		<button><a href="cookieLocale.jsp"><fmt:message key="label.viewPage" /></a></button>
	</c:if>
...

Similarly, let’s run the application and open this changeLocale.jsp to choose Chinese as cookie locale. Then we can access cookieLocale.jsp to view the result:
Project on application server in Chinese | Phrase

4. Conclusion

In this article, we have explored how to localize a JSP page on a basic Java Web Application. Moreover, we also learn how to choose locale dynamically by using request parameter, session attribute, and browser cookie.
Our approach is using JSTL in combination with resource bundle where we use <ftm:setLocale> and <ftm:setBundle> to refer to a resource bundle properties file.
Please view our Github repository for full implementation of this article.
Ready to delve deeper into the world of internationalization and localization in Java? The following guides will get you started on the right path:

Keep exploring

Photo-realistic sheet music featuring developer-style translation code in place of musical notes. The staff lines show snippets like t('auth.signin.button') and JSON structures, combining the aesthetics of musical notation with programming syntax to illustrate the idea of “composable localization.”

Blog post

Localization as code: a composable approach to localization

Why is localization still a manual, disconnected process in a world where everything else is already “as code”? Learn how a composable, developer-friendly approach brings localization into your CI/CD pipeline, with automation, observability, and Git-based workflows built in.

A woman in a light sweater sits in a home office, focused on her laptop, representing a developer or content manager working on WordPress localization tasks in a calm, professional environment.

Blog post

How to build a scalable WordPress i18n workflow

WordPress powers the web, but translating it well takes more than plugins. Discover how to build a scalable localization workflow using gettext, best practices, and the Phrase plugin.

Blog post

Localizing Unity games with the official Phrase plugin

Want to localize your Unity game without the CSV chaos? Discover how the official Phrase Strings Unity plugin simplifies your game’s localization workflow—from string table setup to pulling translations directly into your project. Whether you’re building for German, Serbian, or beyond, this guide shows how to get started fast and localize like a pro.

Blog post

Internationalization beyond code: A developer’s guide to real-world language challenges

Discover how language affects your UI. From text expansion to pluralization, this guide explores key i18n pitfalls and best practices for modern web developers.

A digital artwork featuring the Astro.js logo in bold yellow and purple tones, floating above Earth's horizon with a stunning cosmic nebula in the background. The vibrant space setting symbolizes the global and scalable nature of Astro’s localization capabilities, reinforcing the article’s focus on internationalization in web development.

Blog post

Astro.js localization part 2: dynamic content localization

Learn how to localize your Astro.js website with static and dynamic content translation. Explore Astro’s built-in i18n features and Paraglide for handling UI elements, navigation, and dynamic text seamlessly.