Deployment Descriptors (web.xml)

Configuring Java web applications

← Back to Index

What is a Deployment Descriptor?

A Deployment Descriptor is an XML configuration file that tells the servlet container how to deploy and run your web application. The most important deployment descriptor is web.xml, located in the WEB-INF/ directory.

While modern Java applications often use annotations instead of XML configuration, understanding deployment descriptors is essential for maintaining legacy applications and handling configurations that annotations can't express.

Historical Context

Before Servlet 3.0 (released in 2009), web.xml was mandatory. Every Java web application needed this file to register servlets, configure filters, and define listeners.

Annotations vs web.xml

// Before Servlet 3.0 - Required web.xml
<servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.example.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

// After Servlet 3.0 - Just use annotation
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    // ...
}

When to Use web.xml:

  • Complex URL patterns with wildcards
  • Ops needs to change config without rebuild
  • Specific filter chain ordering required
  • Override third-party library defaults
  • Legacy application maintenance

Basic web.xml Structure

Location

mywebapp.war
└── WEB-INF/
    └── web.xml    <-- Lives here!

Minimal Template

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                             https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">

    <display-name>My Web Application</display-name>
    <description>A sample web application</description>

    <!-- Configuration goes here -->

</web-app>
Important: Namespace Versions

Match your web.xml namespace to your container version:

  • Tomcat 9: Servlet 4.0, xmlns.jcp.org
  • Tomcat 10+: Servlet 5.0+, jakarta.ee

Configuring Servlets

Basic Servlet Registration

<!-- Define the servlet -->
<servlet>
    <servlet-name>ProductServlet</servlet-name>
    <servlet-class>com.example.ProductServlet</servlet-class>
</servlet>

<!-- Map URLs to the servlet -->
<servlet-mapping>
    <servlet-name>ProductServlet</servlet-name>
    <url-pattern>/products</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>ProductServlet</servlet-name>
    <url-pattern>/products/*</url-pattern>
</servlet-mapping>

Servlet with Init Parameters

<servlet>
    <servlet-name>ReportServlet</servlet-name>
    <servlet-class>com.example.ReportServlet</servlet-class>

    <!-- Init parameters -->
    <init-param>
        <param-name>maxResults</param-name>
        <param-value>100</param-value>
    </init-param>
    <init-param>
        <param-name>cacheEnabled</param-name>
        <param-value>true</param-value>
    </init-param>

    <!-- Load on startup (lower = higher priority) -->
    <load-on-startup>1</load-on-startup>
</servlet>
// Accessing init parameters in your servlet:
public void init() throws ServletException {
    int maxResults = Integer.parseInt(getInitParameter("maxResults"));
    boolean cacheEnabled = Boolean.parseBoolean(getInitParameter("cacheEnabled"));
}

URL Pattern Matching Rules

Pattern Type Example Matches Priority
Exact Match /catalog/products Only /catalog/products 1 (Highest)
Path Match /catalog/* /catalog, /catalog/x, /catalog/x/y 2
Extension Match *.do /anything.do, /path/action.do 3
Default / Anything not matched above 4 (Lowest)

Configuring Filters

Basic Filter Registration

<filter>
    <filter-name>LoggingFilter</filter-name>
    <filter-class>com.example.LoggingFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>LoggingFilter</filter-name>
    <url-pattern>/*</url-pattern>  <!-- Apply to all URLs -->
</filter-mapping>

Filter Chain Ordering

Critical: Filters execute in the order they appear in web.xml:

<!-- Filters execute in this order: -->

<!-- 1. First: Character encoding -->
<filter>
    <filter-name>EncodingFilter</filter-name>
    <filter-class>com.example.EncodingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>EncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 2. Second: Authentication -->
<filter>
    <filter-name>AuthFilter</filter-name>
    <filter-class>com.example.AuthFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>AuthFilter</filter-name>
    <url-pattern>/secure/*</url-pattern>
</filter-mapping>

<!-- 3. Third: Authorization -->
<filter>
    <filter-name>AuthzFilter</filter-name>
    <filter-class>com.example.AuthzFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>AuthzFilter</filter-name>
    <url-pattern>/admin/*</url-pattern>
</filter-mapping>

Session Configuration

<session-config>
    <!-- Session timeout in minutes (0 = never expires) -->
    <session-timeout>30</session-timeout>

    <!-- Cookie configuration -->
    <cookie-config>
        <name>MYAPPSESSIONID</name>
        <http-only>true</http-only>   <!-- JavaScript cannot access -->
        <secure>true</secure>         <!-- Only send over HTTPS -->
        <max-age>-1</max-age>         <!-- -1 = session cookie -->
    </cookie-config>

    <!-- Session tracking modes -->
    <tracking-mode>COOKIE</tracking-mode>
</session-config>

Security Best Practices:

  • http-only="true" - Prevents XSS attacks from stealing cookies
  • secure="true" - Prevents session hijacking over HTTP
  • Use COOKIE tracking - URL rewriting exposes session ID

Error Pages

<!-- Error page by HTTP status code -->
<error-page>
    <error-code>404</error-code>
    <location>/WEB-INF/errors/not-found.html</location>
</error-page>

<error-page>
    <error-code>500</error-code>
    <location>/WEB-INF/errors/server-error.jsp</location>
</error-page>

<!-- Error page by exception type -->
<error-page>
    <exception-type>java.lang.NullPointerException</exception-type>
    <location>/WEB-INF/errors/null-error.jsp</location>
</error-page>

<!-- Catch-all for any exception -->
<error-page>
    <exception-type>java.lang.Throwable</exception-type>
    <location>/WEB-INF/errors/generic-error.jsp</location>
</error-page>
Production Security

Never expose stack traces or detailed error messages in production! Show friendly messages to users and log details server-side.

Welcome Files

<welcome-file-list>
    <!-- Checked in order until one is found -->
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
</welcome-file-list>

<!-- Request: http://localhost:8080/myapp/
     Container looks for:
     1. /myapp/index.html
     2. /myapp/index.htm
     3. /myapp/index.jsp
     4. /myapp/default.html
     Returns 404 if none found -->

Security Configuration

Protecting Resources

<!-- Define security constraint -->
<security-constraint>
    <web-resource-collection>
        <web-resource-name>Admin Area</web-resource-name>
        <url-pattern>/admin/*</url-pattern>
        <http-method>GET</http-method>
        <http-method>POST</http-method>
    </web-resource-collection>

    <!-- Who can access -->
    <auth-constraint>
        <role-name>admin</role-name>
    </auth-constraint>

    <!-- Require HTTPS -->
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>

<!-- Authentication method -->
<login-config>
    <auth-method>FORM</auth-method>
    <form-login-config>
        <form-login-page>/login.html</form-login-page>
        <form-error-page>/login-error.html</form-error-page>
    </form-login-config>
</login-config>

<!-- Define roles -->
<security-role>
    <role-name>user</role-name>
</security-role>
<security-role>
    <role-name>admin</role-name>
</security-role>

Form Login Page Requirements

<!-- login.html - Must use specific form action and field names -->
<form action="j_security_check" method="POST">
    <input type="text" name="j_username" required>
    <input type="password" name="j_password" required>
    <button type="submit">Login</button>
</form>

<!-- These names are REQUIRED by the servlet spec:
     - Form action: j_security_check
     - Username field: j_username
     - Password field: j_password -->

Context Parameters

<!-- Application-wide settings -->
<context-param>
    <param-name>adminEmail</param-name>
    <param-value>admin@example.com</param-value>
</context-param>

<context-param>
    <param-name>maxUploadSize</param-name>
    <param-value>10485760</param-value> <!-- 10 MB -->
</context-param>

<context-param>
    <param-name>environment</param-name>
    <param-value>production</param-value>
</context-param>
// Accessing context parameters:
ServletContext context = getServletContext();
String adminEmail = context.getInitParameter("adminEmail");
String env = context.getInitParameter("environment");

Listeners

<!-- Application lifecycle -->
<listener>
    <listener-class>com.example.AppStartupListener</listener-class>
</listener>

<!-- Session lifecycle -->
<listener>
    <listener-class>com.example.SessionTracker</listener-class>
</listener>
Listener Interface Events Common Use
ServletContextListener App start/stop Initialize resources
HttpSessionListener Session create/destroy Count active users
ServletRequestListener Request start/end Request timing, logging

Summary

  • web.xml: The main deployment descriptor in WEB-INF/
  • Annotations vs XML: Modern apps use annotations; XML for special cases
  • Filter ordering: Defined by order in web.xml
  • URL patterns: Exact > path > extension > default
  • Security: Configure authentication, roles, HTTPS
  • Session config: Timeout, cookie options, tracking mode
  • Error pages: Custom pages for HTTP codes and exceptions
  • Context params: Application-wide configuration