Jakarta Servlet

Jakarta Servlet is a corner stone web framework that can act as a presentation-oriented as well as a service-oriented web application. Jakarta Servlet intends to reduce the boilerplate code needed to convert the HTTP request into a Java object and to offer a Java object as an HTTP response, and to manage all the lifecycle around them.

What Is a Servlet?

A servlet is a Java programming language class that directly or indirectly implements the jakarta.servlet.Servlet interface. The jakarta.servlet and jakarta.servlet.http packages provide interfaces and classes for writing servlets. All servlets must implement the jakarta.servlet.Servlet interface, which defines lifecycle methods such as init, service, and destroy. When implementing a generic service, you can extend the jakarta.servlet.GenericServlet class which already implements the Servlet interface. When implementing an HTTP service, you can extend the jakarta.servlet.http.HttpServlet class which already extends the GenericServlet class.

In a typical Jakarta Servlet based web application, the class must extend jakarta.servlet.http.HttpServlet and override one of the doXxx methods where Xxx represents the HTTP method of interest.

Servlet Lifecycle

The lifecycle of a servlet is controlled by the servlet container in which the servlet has been deployed. When a request is mapped to a servlet, the servlet container performs the following steps:

  1. If an instance of the servlet does not exist, the servlet container:

    1. Loads the servlet class

    2. Creates an instance of the servlet class

    3. Initializes the servlet instance by calling the init method

  2. The servlet container invokes the service method, passing request and response objects.

Initialization is covered in [creating-and-initializing-a-servlet]. Service methods are discussed in [writing-service-methods].

If it needs to remove the servlet, the servlet container finalizes the servlet by calling the servlet’s destroy method. For more information, see [finalizing-a-servlet].

Sharing Information

Web components, like most objects, usually work with data to accomplish their tasks. Web components can store this information in a data store or in a scoped bean, among others.

Using CDI Managed Beans

CDI can be used to define scoped beans which can be injected in any container-managed objects such as web components. These scoped beans can then be used to store and transfer information.

CDI defines three basic scopes which can be used for this:

@jakarta.enterprise.context.RequestScoped

In a servlet container, this is mapped to the jakarta.servlet.ServletRequest. It lives from the time that the client has sent the request until it has retrieved the corresponding response. It is not shared elsewhere.

@jakarta.enterprise.context.SessionScoped

In a servlet container, this is mapped to the jakarta.servlet.http.HttpSession. It lives for as long as the client is interacting with the web application with the same browser instance, and the session hasn’t timed out at the server side. It is shared among all requests in the same session.

@jakarta.enterprise.context.ApplicationScoped

In a servlet container, this is mapped to the jakarta.servlet.ServletContext. It lives for as long as the web application lives. It is shared among all requests in all sessions.

The below example illustrates how CDI @SessionScoped can be used to define a session scoped bean which stores the user preferences.

@Named
@SessionScoped
public class UserPreferences implements Serializable {

    private Locale language;
    private ZoneId timeZone;
    private boolean darkMode;

    // ...
}

It can be injected in any web component. The below example illustrates how a servlet can be used to take a time zone offset in minutes and update the user preferences with it.

@WebServlet("/timeZoneHandler")
public class TimeZoneHandler extends HttpServlet {

    @Inject
    private UserPreferences userPreferences;

    @Override
    public void doPost
        (HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        var offsetInMinutes = request.getParameter("offsetInMinutes");

        if (offsetInMinutes == null || !offsetInMinutes.matches("\\-?[0-9]{1,3}")) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        var offsetInSeconds = TimeUnit.MINUTES.toSeconds(Long.valueOf(offsetInMinutes));
        ZoneId timeZone = ZoneOffset.ofTotalSeconds((int) offsetInSeconds);
        userPreferences.setTimeZone(timeZone);
        response.setStatus(HttpServletResponse.SC_NO_CONTENT);
    }
}

For example JavaScript’s new Date().getTimeZoneOffset() returns the local (negative) time zone offset in minutes. This servlet can then be invoked as follows in JavaScript in order to inform the server about the client’s time zone:

fetch("/context-path/timeZoneHandler", {
    method: "POST",
    body: new URLSearchParams({
        offsetInMinutes: -new Date().getTimezoneOffset()
    })
});

Using Scope Objects Directly

If CDI is not available, an alternative is to use Jakarta Servlet’s own scope objects directly. You can use getAttribute and setAttribute methods of the Jakarta Servlet class representing the scope. Scope Objects lists the scope objects.

Scope Objects

Scope Object

Class

Accessible From

Application

jakarta.servlet.ServletContext

Web components within the web application. See [accessing-the-web-context].

Session

jakarta.servlet.http.HttpSession

Web components handling a request that belongs to the session. See [maintaining-client-state].

Request

jakarta.servlet.ServletRequest

Web components handling the request.

The below example illustrates how the previously shown servlet needs to be adjusted to manually manage the UserPreferences bean.

@WebServlet("/timeZoneHandler")
public class TimeZoneHandler extends HttpServlet {

    @Override
    public void doPost
        (HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        var offsetInMinutes = request.getParameter("offsetInMinutes");

        if (offsetInMinutes == null || !offsetInMinutes.matches("\\-?[0-9]{1,3}")) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        var offsetInSeconds = TimeUnit.MINUTES.toSeconds(Long.valueOf(offsetInMinutes));
        ZoneId timeZone = ZoneOffset.ofTotalSeconds((int) offsetInSeconds);
        HttpSession session = request.getSession();
        UserPreferenes userPreferences = session.getAttribute("userPreferences");

        if (userPreferences == null) {
            userPreferences = new UserPreferences();
            session.setAttribute("userPreferences", userPreferences);
        }

        userPreferences.setTimeZone(timeZone);
        response.setStatus(HttpServletResponse.SC_NO_CONTENT);
    }
}

Controlling Concurrent Access to Shared Resources

In a multithreaded server, shared resources can be accessed concurrently. In addition to scope object attributes, shared resources include in-memory data, such as instance or class variables, and external objects, such as files, database connections, and network connections.

Concurrent access can arise in several situations.

  • Multiple web components accessing objects stored in the application scope.

  • Multiple web components accessing objects stored in the session scope.

  • Multiple threads within a web component accessing instance variables.

A web container will typically create a thread to handle each request. When resources can be accessed concurrently, they can be used in an inconsistent fashion. First step is to ensure that the variable representing the resource has the correct scope and use a as narrow as possible scope. For example, request scoped information should not be stored in a session scoped bean nor be assigned as an instance variable of a servlet, and session scoped information should not be stored in an application scoped bean.

If concurrent access is inevitable, then you prevent this by using synchronized or atomic objects such as wrapping a Map in Collections.synchronizedMap() before assigning it to a property of a session scoped bean.

Creating and Initializing a Servlet

We are working on a fresh, updated Jakarta EE Tutorial. This section hasn’t yet been updated.

Use the @WebServlet annotation to define a servlet component in a web application. This annotation is specified on a class and contains metadata about the servlet being declared. The annotated servlet must specify at least one URL pattern. This is done by using the urlPatterns or value attribute on the annotation. All other attributes are optional, with default settings. Use the value attribute when the only attribute on the annotation is the URL pattern; otherwise, use the urlPatterns attribute when other attributes are also used.

Classes annotated with @WebServlet must extend the jakarta.servlet.http.HttpServlet class. For example, the following code snippet defines a servlet with the URL pattern /report:

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;

@WebServlet("/report")
public class MoodServlet extends HttpServlet {
    ...
}

The web container initializes a servlet after loading and instantiating the servlet class and before delivering requests from clients. To customize this process to allow the servlet to read persistent configuration data, initialize resources, and perform any other one-time activities, you can either override the init method of the Servlet interface or specify the initParams attribute of the @WebServlet annotation. The initParams attribute contains a @WebInitParam annotation. If it cannot complete its initialization process, a servlet throws an UnavailableException.

Use an initialization parameter to provide data needed by a particular servlet. By contrast, a context parameter provides data that is available to all components of a web application.

Writing Service Methods

The service provided by a servlet is implemented in the service method of a GenericServlet, in the doMethod methods (where Method can take the value Get, Delete, Options, Post, Put, or Trace) of an HttpServlet object, or in any other protocol-specific methods defined by a class that implements the Servlet interface. The term service method is used for any method in a servlet class that provides a service to a client.

The general pattern for a service method is to extract information from the request, access external resources, and then populate the response, based on that information. For HTTP servlets, the correct procedure for populating the response is to do the following:

  1. Retrieve an output stream from the response.

  2. Fill in the response headers.

  3. Write any body content to the output stream.

Response headers must always be set before the response has been committed. The web container will ignore any attempt to set or add headers after the response has been committed. The next two sections describe how to get information from requests and generate responses.

Getting Information from Requests

A request contains data passed between a client and the servlet. All requests implement the ServletRequest interface. This interface defines methods for accessing the following information:

  • Parameters, which are typically used to convey information between clients and servlets

  • Object-valued attributes, which are typically used to pass information between the web container and a servlet or between collaborating servlets

  • Information about the protocol used to communicate the request and about the client and server involved in the request

  • Information relevant to localization

You can also retrieve an input stream from the request and manually parse the data. To read character data, use the BufferedReader object returned by the request’s getReader method. To read binary data, use the ServletInputStream returned by getInputStream.

HTTP servlets are passed an HTTP request object, HttpServletRequest, which contains the request URL, HTTP headers, query string, and so on. An HTTP request URL contains the following parts:

http://[host]:[port][request-path]?[query-string]

The request path is further composed of the following elements.

  • Context path: A concatenation of a forward slash (/) with the context root of the servlet’s web application.

  • Servlet path: The path section that corresponds to the component alias that activated this request. This path starts with a forward slash (/).

  • Path info: The part of the request path that is not part of the context path or the servlet path.

You can use the getContextPath, getServletPath, and getPathInfo methods of the HttpServletRequest interface to access this information. Except for URL encoding differences between the request URI and the path parts, the request URI is always comprised of the context path plus the servlet path plus the path info.

Query strings are composed of a set of parameters and values. Individual parameters are retrieved from a request by using the getParameter method. There are two ways to generate query strings.

  • A query string can explicitly appear in a web page.

  • A query string is appended to a URL when a form with a GET HTTP method is submitted.

Constructing Responses

A response contains data passed between a server and the client. All responses implement the ServletResponse interface. This interface defines methods that allow you to do the following.

  • Retrieve an output stream to use to send data to the client. To send character data, use the PrintWriter returned by the response’s getWriter method. To send binary data in a Multipurpose Internet Mail Extensions (MIME) body response, use the ServletOutputStream returned by getOutputStream. To mix binary and text data, as in a multipart response, use a ServletOutputStream and manage the character sections manually.

  • Indicate the content type (for example, text/html) being returned by the response with the setContentType(String) method. This method must be called before the response is committed. A registry of content type names is kept by the Internet Assigned Numbers Authority (IANA) at https://www.iana.org/assignments/media-types/.

  • Indicate whether to buffer output with the setBufferSize(int) method. By default, any content written to the output stream is immediately sent to the client. Buffering allows content to be written before anything is sent back to the client, thus providing the servlet with more time to set appropriate status codes and headers or forward to another web resource. The method must be called before any content is written or before the response is committed.

  • Set localization information, such as locale and character encoding. See Internationalizing and Localizing Web Applications for details.

HTTP response objects, jakarta.servlet.http.HttpServletResponse, have fields representing HTTP headers, such as the following.

  • Status codes, which are used to indicate the reason a request is not satisfied or that a request has been redirected.

  • Cookies, which are used to store application-specific information at the client. Sometimes, cookies are used to maintain an identifier for tracking a user’s session (see Session Tracking).

Handling Servlet Lifecycle Events

You can monitor and react to events in a servlet’s lifecycle by defining listener objects whose methods get invoked when lifecycle events occur. To use these listener objects, you must define and specify the listener class.

Defining the Listener Class

You define a listener class as an implementation of a listener interface. Servlet Lifecycle Events lists the events that can be monitored and the corresponding interface that must be implemented. When a listener method is invoked, it is passed an event that contains information appropriate to the event. For example, the methods in the HttpSessionListener interface are passed an HttpSessionEvent, which contains an HttpSession.

Servlet Lifecycle Events
Object Event Listener Interface and Event Class

Web context

Initialization and destruction

jakarta.servlet.ServletContextListener and ServletContextEvent

Web context

Attribute added, removed, or replaced

jakarta.servlet.ServletContextAttributeListener and ServletContextAttributeEvent

Session

Creation, invalidation, activation, passivation, and timeout

jakarta.servlet.http.HttpSessionListener, jakarta.servlet.http.HttpSessionActivationListener, and HttpSessionEvent

Session

Attribute added, removed, or replaced

jakarta.servlet.http.HttpSessionAttributeListener and HttpSessionBindingEvent

Request

A servlet request has started being processed by web components

jakarta.servlet.ServletRequestListener and ServletRequestEvent

Request

Attribute added, removed, or replaced

jakarta.servlet.ServletRequestAttributeListener and ServletRequestAttributeEvent

Use the @WebListener annotation to define a listener to get events for various operations on the particular web application context. Classes annotated with @WebListener must implement one of the following interfaces:

jakarta.servlet.ServletContextListener
jakarta.servlet.ServletContextAttributeListener
jakarta.servlet.ServletRequestListener
jakarta.servlet.ServletRequestAttributeListener
jakarta.servlet..http.HttpSessionListener
jakarta.servlet..http.HttpSessionAttributeListener

For example, the following code snippet defines a listener that implements two of these interfaces:

import jakarta.servlet.ServletContextAttributeListener;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;

@WebListener()
public class SimpleServletListener implements ServletContextListener,
        ServletContextAttributeListener {
    ...
}

Handling Servlet Errors

Any number of exceptions can occur when a servlet executes. When an exception occurs, the web container generates a default page containing the following message:

A Servlet Exception Has Occurred

But you can also specify that the container should return a specific error page for a given exception.

Filtering Requests and Responses

A filter is an object that can transform the header and content (or both) of a request or response. Filters differ from web components in that filters usually do not themselves create a response. Instead, a filter provides functionality that can be "attached" to any kind of web resource. Consequently, a filter should not have any dependencies on a web resource for which it is acting as a filter; this way, it can be composed with more than one type of web resource.

The main tasks that a filter can perform are as follows.

  • Query the request and act accordingly.

  • Block the request-and-response pair from passing any further.

  • Modify the request headers and data. You do this by providing a customized version of the request.

  • Modify the response headers and data. You do this by providing a customized version of the response.

  • Interact with external resources.

Applications of filters include authentication, logging, image conversion, data compression, encryption, tokenizing streams, XML transformations, and so on.

You can configure a web resource to be filtered by a chain of zero, one, or more filters in a specific order. This chain is specified when the web application containing the component is deployed and is instantiated when a web container loads the component.

Programming Filters

The filtering API is defined by the Filter, FilterChain, and FilterConfig interfaces in the jakarta.servlet package. You define a filter by implementing the Filter interface.

Use the @WebFilter annotation to define a filter in a web application. This annotation is specified on a class and contains metadata about the filter being declared. The annotated filter must specify at least one URL pattern. This is done by using the urlPatterns or value attribute on the annotation. All other attributes are optional, with default settings. Use the value attribute when the only attribute on the annotation is the URL pattern; use the urlPatterns attribute when other attributes are also used.

Classes annotated with the @WebFilter annotation must implement the jakarta.servlet.Filter interface.

To add configuration data to the filter, specify the initParams attribute of the @WebFilter annotation. The initParams attribute contains a @WebInitParam annotation. The following code snippet defines a filter, specifying an initialization parameter:

import jakarta.servlet.Filter;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.annotation.WebInitParam;

@WebFilter(filterName = "TimeOfDayFilter", urlPatterns = {"/*"},
initParams = {@WebInitParam(name = "mood", value = "awake")})
public class TimeOfDayFilter implements Filter {
    ...
}

The most important method in the Filter interface is doFilter, which is passed request, response, and filter chain objects. This method can perform the following actions.

  • Examine the request headers.

  • Customize the request object if the filter wishes to modify request headers or data.

  • Customize the response object if the filter wishes to modify response headers or data.

  • Invoke the next entity in the filter chain. If the current filter is the last filter in the chain that ends with the target web component or static resource, the next entity is the resource at the end of the chain; otherwise, it is the next filter that was configured in the WAR. The filter invokes the next entity by calling the doFilter method on the chain object, passing in the request and response it was called with or the wrapped versions it may have created. Alternatively, the filter can choose to block the request by not making the call to invoke the next entity. In the latter case, the filter is responsible for filling out the response.

  • Examine response headers after invoking the next filter in the chain.

  • Throw an exception to indicate an error in processing.

In addition to doFilter, you must implement the init and destroy methods. The init method is called by the container when the filter is instantiated. If you wish to pass initialization parameters to the filter, you retrieve them from the FilterConfig object passed to init.

Programming Customized Requests and Responses

There are many ways for a filter to modify a request or a response. For example, a filter can add an attribute to the request or can insert data in the response.

A filter that modifies a response must usually capture the response before it is returned to the client. To do this, you pass a stand-in stream to the servlet that generates the response. The stand-in stream prevents the servlet from closing the original response stream when it completes and allows the filter to modify the servlet’s response.

To pass this stand-in stream to the servlet, the filter creates a response wrapper that overrides the getWriter or getOutputStream method to return this stand-in stream. The wrapper is passed to the doFilter method of the filter chain. Wrapper methods default to calling through to the wrapped request or response object.

To override request methods, you wrap the request in an object that extends either ServletRequestWrapper or HttpServletRequestWrapper. To override response methods, you wrap the response in an object that extends either ServletResponseWrapper or HttpServletResponseWrapper.

Specifying Filter Mappings

A web container uses filter mappings to decide how to apply filters to web resources. A filter mapping matches a filter to a web component by name or to web resources by URL pattern. The filters are invoked in the order in which filter mappings appear in the filter mapping list of a WAR. You specify a filter mapping list for a WAR in its deployment descriptor by either using NetBeans IDE or coding the list by hand with XML.

If you want to log every request to a web application, you map the hit counter filter to the URL pattern /*.

You can map a filter to one or more web resources, and you can map more than one filter to a web resource. This is illustrated in Figure 1, “Filter-to-Servlet Mapping”, in which filter F1 is mapped to servlets S1, S2, and S3; filter F2 is mapped to servlet S2; and filter F3 is mapped to servlets S1 and S2.

Diagram of filter-to-servlet mapping with filters F1-F3 and servlets S1-S3. F1 filters S1-S3, then F2 filters S2, then F3 filters S1 and S2.
Figure 1. Filter-to-Servlet Mapping

Recall that a filter chain is one of the objects passed to the doFilter method of a filter. This chain is formed indirectly by means of filter mappings. The order of the filters in the chain is the same as the order in which filter mappings appear in the web application deployment descriptor.

When a filter is mapped to servlet S1, the web container invokes the doFilter method of F1. The doFilter method of each filter in S1’s filter chain is invoked by the preceding filter in the chain by means of the chain.doFilter method. Because S1’s filter chain contains filters F1 and F3, F1’s call to chain.doFilter invokes the doFilter method of filter F3. When F3’s doFilter method completes, control returns to F1’s doFilter method.

To Specify Filter Mappings Using NetBeans IDE

  1. Expand the application’s project node in the Project tab.

  2. Expand the Web Pages and WEB-INF nodes under the project node.

  3. Double-click web.xml.

  4. Click Filters at the top of the editor window.

  5. Expand the Servlet Filters node in the editor window.

  6. Click Add Filter Element to map the filter to a web resource by name or by URL pattern.

  7. In the Add Servlet Filter dialog box, enter the name of the filter in the Filter Name field.

  8. Click Browse to locate the servlet class to which the filter applies.

    You can include wildcard characters so that you can apply the filter to more than one servlet.

  9. Click OK.

  10. To constrain how the filter is applied to requests, follow these steps:

    1. Expand the Filter Mappings node.

    2. Select the filter from the list of filters.

    3. Click Add.

    4. In the Add Filter Mapping dialog box, select one of the following dispatcher types:

    REQUEST

    Only when the request comes directly from the client

    ASYNC

    Only when the asynchronous request comes from the client

    FORWARD

    Only when the request has been forwarded to a component (see Transferring Control to Another Web Component)

    INCLUDE

    Only when the request is being processed by a component that has been included (see Including Other Resources in the Response)

    ERROR

    Only when the request is being processed with the error page mechanism (see Handling Servlet Errors)

    You can direct the filter to be applied to any combination of the preceding situations by selecting multiple dispatcher types. If no types are specified, the default option is REQUEST.

Invoking Other Web Resources

Web components can invoke other web resources both indirectly and directly. A web component indirectly invokes another web resource by embedding a URL that points to another web component in content returned to a client. While it is executing, a web component directly invokes another resource by either including the content of another resource or forwarding a request to another resource.

To invoke a resource available on the server that is running a web component, you must first obtain a RequestDispatcher object by using the getRequestDispatcher("URL") method. You can get a RequestDispatcher object from either a request or the web context; however, the two methods have slightly different behavior. The method takes the path to the requested resource as an argument. A request can take a relative path (that is, one that does not begin with a /), but the web context requires an absolute path. If the resource is not available or if the server has not implemented a RequestDispatcher object for that type of resource, getRequestDispatcher will return null. Your servlet should be prepared to deal with this condition.

Including Other Resources in the Response

It is often useful to include another web resource, such as banner content or copyright information, in the response returned from a web component. To include another resource, invoke the include method of a RequestDispatcher object:

include(request, response);

If the resource is static, the include method enables programmatic server-side includes. If the resource is a web component, the effect of the method is to send the request to the included web component, execute the web component, and then include the result of the execution in the response from the containing servlet. An included web component has access to the request object but is limited in what it can do with the response object.

  • It can write to the body of the response and commit a response.

  • It cannot set headers or call any method, such as setCookie, that affects the headers of the response.

Transferring Control to Another Web Component

In some applications, you might want to have one web component do preliminary processing of a request and have another component generate the response. For example, you might want to partially process a request and then transfer to another component, depending on the nature of the request.

To transfer control to another web component, you invoke the forward method of a RequestDispatcher. When a request is forwarded, the request URL is set to the path of the forwarded page. The original URI and its constituent parts are saved as the following request attributes:

jakarta.servlet.forward.request_uri
jakarta.servlet.forward.context_path
jakarta.servlet.forward.servlet_path
jakarta.servlet.forward.path_info
jakarta.servlet.forward.query_string

The forward method should be used to give another resource responsibility for replying to the user. If you have already accessed a ServletOutputStream or PrintWriter object within the servlet, you cannot use this method; doing so throws an IllegalStateException.

Accessing the Web Context

The context in which web components execute is an object that implements the ServletContext interface. You retrieve the web context by using the getServletContext method. The web context provides methods for accessing

  • Initialization parameters

  • Resources associated with the web context

  • Object-valued attributes

  • Logging capabilities

The counter’s access methods are synchronized to prevent incompatible operations by servlets that are running concurrently. A filter retrieves the counter object by using the context’s getAttribute method. The incremented value of the counter is recorded in the log.

Maintaining Client State

Many applications require that a series of requests from a client be associated with one another. For example, a web application can save the state of a user’s shopping cart across requests. Web-based applications are responsible for maintaining such state, called a session, because HTTP is stateless. To support applications that need to maintain state, Jakarta Servlet technology provides an API for managing sessions and allows several mechanisms for implementing sessions.

Accessing a Session

Sessions are represented by an HttpSession object. You access a session by calling the getSession method of a request object. This method returns the current session associated with this request; or, if the request does not have a session, this method creates one.

Associating Objects with a Session

You can associate object-valued attributes with a session by name. Such attributes are accessible by any web component that belongs to the same web context and is handling a request that is part of the same session.

Recall that your application can notify web context and session listener objects of servlet lifecycle events (Handling Servlet Lifecycle Events). You can also notify objects of certain events related to their association with a session, such as the following.

  • When the object is added to or removed from a session. To receive this notification, your object must implement the jakarta.servlet.http.HttpSessionBindingListener interface.

  • When the session to which the object is attached will be passivated or activated. A session will be passivated or activated when it is moved between virtual machines or saved to and restored from persistent storage. To receive this notification, your object must implement the jakarta.servlet.http.HttpSessionActivationListener interface.

Session Management

Because an HTTP client has no way to signal that it no longer needs a session, each session has an associated timeout so that its resources can be reclaimed. The timeout period can be accessed by using a session’s getMaxInactiveInterval and setMaxInactiveInterval methods.

  • To ensure that an active session is not timed out, you should periodically access the session by using service methods because this resets the session’s time-to-live counter.

  • When a particular client interaction is finished, you use the session’s invalidate method to invalidate a session on the server side and remove any session data.

To Set the Timeout Period Using NetBeans IDE

To set the timeout period in the deployment descriptor using NetBeans IDE, follow these steps.

  1. Open the project if you haven’t already.

  2. Expand the node of your project in the Projects tab.

  3. Expand the Web Pages and WEB-INF nodes that are under the project node.

  4. Double-click web.xml.

  5. Click General at the top of the editor.

  6. In the Session Timeout field, enter an integer value.

    The integer value represents the number of minutes of inactivity that must pass before the session times out.

Session Tracking

To associate a session with a user, a web container can use several methods, all of which involve passing an identifier between the client and the server. The identifier can be maintained on the client as a cookie, or the web component can include the identifier in every URL that is returned to the client.

If your application uses session objects, you must ensure that session tracking is enabled by having the application rewrite URLs whenever the client turns off cookies. You do this by calling the response’s encodeURL(URL) method on all URLs returned by a servlet. This method includes the session ID in the URL only if cookies are disabled; otherwise, the method returns the URL unchanged.

Finalizing a Servlet

The web container may determine that a servlet should be removed from service (for example, when a container wants to reclaim memory resources or when it is being shut down). In such a case, the container calls the destroy method of the Servlet interface. In this method, you release any resources the servlet is using and save any persistent state. The destroy method releases the database object created in the init method.

A servlet’s service methods should all be complete when a servlet is removed. The server tries to ensure this by calling the destroy method only after all service requests have returned or after a server-specific grace period, whichever comes first. If your servlet has operations that may run longer than the server’s grace period, the operations could still be running when destroy is called. You must make sure that any threads still handling client requests complete.

The remainder of this section explains how to do the following.

  • Keep track of how many threads are currently running the service method.

  • Provide a clean shutdown by having the destroy method notify long-running threads of the shutdown and wait for them to complete.

  • Have the long-running methods poll periodically to check for shutdown and, if necessary, stop working, clean up, and return.

Tracking Service Requests

To track service requests:

  1. Include a field in your servlet class that counts the number of service methods that are running.

    The field should have synchronized access methods to increment, decrement, and return its value:

    public class ShutdownExample extends HttpServlet {
        private int serviceCounter = 0;
        ...
        // Access methods for serviceCounter
        protected synchronized void enteringServiceMethod() {
            serviceCounter++;
        }
        protected synchronized void leavingServiceMethod() {
            serviceCounter--;
        }
        protected synchronized int numServices() {
            return serviceCounter;
        }
    }

    The service method should increment the service counter each time the method is entered and should decrement the counter each time the method returns. This is one of the few times that your HttpServlet subclass should override the service method. The new method should call super.service to preserve the functionality of the original service method:

    protected void service(HttpServletRequest req,
                           HttpServletResponse resp)
                           throws ServletException,IOException {
        enteringServiceMethod();
        try {
            super.service(req, resp);
        } finally {
            leavingServiceMethod();
        }
    }

Notifying Methods to Shut Down

To ensure a clean shutdown, your destroy method should not release any shared resources until all the service requests have completed:

  1. Check the service counter.

  2. Notify long-running methods that it is time to shut down.

    For this notification, another field is required. The field should have the usual access methods:

    public class ShutdownExample extends HttpServlet {
        private boolean shuttingDown;
        ...
        //Access methods for shuttingDown
        protected synchronized void setShuttingDown(boolean flag) {
            shuttingDown = flag;
        }
        protected synchronized boolean isShuttingDown() {
            return shuttingDown;
        }
    }

    Here is an example of the destroy method using these fields to provide a clean shutdown:

    public void destroy() {
        /* Check to see whether there are still service methods /*
        /* running, and if there are, tell them to stop. */
        if (numServices() > 0) {
            setShuttingDown(true);
        }
    
        /* Wait for the service methods to stop. */
        while (numServices() > 0) {
            try {
                Thread.sleep(interval);
            } catch (InterruptedException e) {
            }
        }
    }

Creating Polite Long-Running Methods

The final step in providing a clean shutdown is to make any long-running methods behave politely. Methods that might run for a long time should check the value of the field that notifies them of shutdowns and should interrupt their work, if necessary:

public void doPost(...) {
    ...
    for(i = 0; ((i < lotsOfStuffToDo) &&
         !isShuttingDown()); i++) {
        try {
            partOfLongRunningOperation(i);
        } catch (InterruptedException e) {
            ...
        }
    }
}

Uploading Files with Jakarta Servlet Technology

Supporting file uploads is a very basic and common requirement for many web applications. In prior versions of the Servlet specification, implementing file upload required the use of external libraries or complex input processing. The Jakarta Servlet specification now helps to provide a viable solution to the problem in a generic and portable way. Jakarta Servlet technology now supports file upload out of the box, so any web container that implements the specification can parse multipart requests and make mime attachments available through the HttpServletRequest object.

A new annotation, jakarta.servlet.annotation.MultipartConfig, is used to indicate that the servlet on which it is declared expects requests to be made using the multipart/form-data MIME type. Servlets that are annotated with @MultipartConfig can retrieve the Part components of a given multipart/form-data request by calling the request.getPart(String name) or request.getParts() method.

The @MultipartConfig Annotation

The @MultipartConfig annotation supports the following optional attributes.

  • location: An absolute path to a directory on the file system. The location attribute does not support a path relative to the application context. This location is used to store files temporarily while the parts are processed or when the size of the file exceeds the specified fileSizeThreshold setting. The default location is "".

  • fileSizeThreshold: The file size in bytes after which the file will be temporarily stored on disk. The default size is 0 bytes.

  • maxFileSize: The maximum size allowed for uploaded files, in bytes. If the size of any uploaded file is greater than this size, the web container will throw an exception (IllegalStateException). The default size is unlimited.

  • maxRequestSize: The maximum size allowed for a multipart/form-data request, in bytes. The web container will throw an exception if the overall size of all uploaded files exceeds this threshold. The default size is unlimited.

For, example, the @MultipartConfig annotation could be constructed as follows:

@MultipartConfig(location="/tmp", fileSizeThreshold=1024*1024,
    maxFileSize=1024*1024*5, maxRequestSize=1024*1024*5*5)

Instead of using the @MultipartConfig annotation to hard-code these attributes in your file upload servlet, you could add the following as a child element of the servlet configuration element in the web.xml file:

<multipart-config>
    <location>/tmp</location>
    <max-file-size>20848820</max-file-size>
    <max-request-size>418018841</max-request-size>
    <file-size-threshold>1048576</file-size-threshold>
</multipart-config>

The getParts and getPart Methods

The Servlet specification supports two additional HttpServletRequest methods:

  • Collection<Part> getParts()

  • Part getPart(String name)

The request.getParts() method returns collections of all Part objects. If you have more than one input of type file, multiple Part objects are returned. Because Part objects are named, the getPart(String name) method can be used to access a particular Part. Alternatively, the getParts() method, which returns an Iterable<Part>, can be used to get an Iterator over all the Part objects.

The jakarta.servlet.http.Part interface is a simple one, providing methods that allow introspection of each Part. The methods do the following:

  • Retrieve the name, size, and content-type of the Part

  • Query the headers submitted with a Part

  • Delete a Part

  • Write a Part out to disk

For example, the Part interface provides the write(String filename) method to write the file with the specified name. The file can then be saved in the directory that is specified with the location attribute of the @MultipartConfig annotation or, in the case of the fileupload example, in the location specified by the Destination field in the form.

Asynchronous Processing

Web containers in application servers normally use a server thread per client request. Under heavy load conditions, containers need a large amount of threads to serve all the client requests. Scalability limitations include running out of memory or exhausting the pool of container threads. To create scalable web applications, you must ensure that no threads associated with a request are sitting idle, so the container can use them to process new requests.

There are two common scenarios in which a thread associated with a request can be sitting idle.

  • The thread needs to wait for a resource to become available or process data before building the response. For example, an application may need to query a database or access data from a remote web service before generating the response.

  • The thread needs to wait for an event before generating the response. For example, an application may have to wait for a Jakarta Messaging message, new information from another client, or new data available in a queue before generating the response.

These scenarios represent blocking operations that limit the scalability of web applications. Asynchronous processing refers to assigning these blocking operations to a new thread and retuning the thread associated with the request immediately to the container.

Asynchronous Processing in Servlets

Jakarta EE provides asynchronous processing support for servlets and filters. If a servlet or a filter reaches a potentially blocking operation when processing a request, it can assign the operation to an asynchronous execution context and return the thread associated with the request immediately to the container without generating a response. The blocking operation completes in the asynchronous execution context in a different thread, which can generate a response or dispatch the request to another servlet.

To enable asynchronous processing on a servlet, set the parameter asyncSupported to true on the @WebServlet annotation as follows:

@WebServlet(urlPatterns={"/asyncservlet"}, asyncSupported=true)
public class AsyncServlet extends HttpServlet { ... }

The jakarta.servlet.AsyncContext class provides the functionality that you need to perform asynchronous processing inside service methods. To obtain an instance of AsyncContext, call the startAsync() method on the request object of your service method; for example:

public void doGet(HttpServletRequest req, HttpServletResponse resp) {
   ...
   AsyncContext acontext = req.startAsync();
   ...
}

This call puts the request into asynchronous mode and ensures that the response is not committed after exiting the service method. You have to generate the response in the asynchronous context after the blocking operation completes or dispatch the request to another servlet.

Functionality Provided by the AsyncContext Class describes the basic functionality provided by the AsyncContext class.

Functionality Provided by the AsyncContext Class
Method Signature Description

void start(Runnable run)

The container provides a different thread in which the blocking operation can be processed.

You provide code for the blocking operation as a class that implements the Runnable interface. You can provide this class as an inner class when calling the start method or use another mechanism to pass the AsyncContext instance to your class.

ServletRequest getRequest()

Returns the request used to initialize this asynchronous context. In the example above, the request is the same as in the service method.

You can use this method inside the asynchronous context to obtain parameters from the request.

ServletResponse getResponse()

Returns the response used to initialize this asynchronous context. In the example above, the response is the same as in the service method.

You can use this method inside the asynchronous context to write to the response with the results of the blocking operation.

void complete()

Completes the asynchronous operation and closes the response associated with this asynchronous context.

You call this method after writing to the response object inside the asynchronous context.

void dispatch(String path)

Dispatches the request and response objects to the given path.

You use this method to have another servlet write to the response after the blocking operation completes.

Waiting for a Resource

This section demonstrates how to use the functionality provided by the AsyncContext class for the following use case:

  1. A servlet receives a parameter from a GET request.

  2. The servlet uses a resource, such as a database or a web service, to retrieve information based on the value of the parameter. The resource can be slow at times, so this may be a blocking operation.

  3. The servlet generates a response using the result from the resource.

The following code shows a basic servlet that does not use asynchronous processing:

@WebServlet(urlPatterns={"/syncservlet"})
public class SyncServlet extends HttpServlet {
   private MyRemoteResource resource;
   @Override
   public void init(ServletConfig config) {
      resource = MyRemoteResource.create("config1=x,config2=y");
   }

   @Override
   public void doGet(HttpServletRequest request,
                     HttpServletResponse response) {
      response.setContentType("text/html;charset=UTF-8");
      String param = request.getParameter("param");
      String result = resource.process(param);
      /* ... print to the response ... */
   }
}

The following code shows the same servlet using asynchronous processing:

@WebServlet(urlPatterns={"/asyncservlet"}, asyncSupported=true)
public class AsyncServlet extends HttpServlet {
   /* ... Same variables and init method as in SyncServlet ... */
   @Override
   public void doGet(HttpServletRequest request,
                     HttpServletResponse response) {
      response.setContentType("text/html;charset=UTF-8");
      final AsyncContext acontext = request.startAsync();
      acontext.start(new Runnable() {
         public void run() {
            String param = acontext.getRequest().getParameter("param");
            String result = resource.process(param);
            HttpServletResponse response = acontext.getResponse();
            /* ... print to the response ... */
            acontext.complete();
         }
      });
   }
}

AsyncServlet adds asyncSupported=true to the @WebServlet annotation. The rest of the differences are inside the service method.

  • request.startAsync() causes the request to be processed asynchronously; the response is not sent to the client at the end of the service method.

  • acontext.start(new Runnable() {…​}) gets a new thread from the container.

  • The code inside the run() method of the inner class executes in the new thread. The inner class has access to the asynchronous context to read parameters from the request and write to the response. Calling the complete() method of the asynchronous context commits the response and sends it to the client.

The service method of AsyncServlet returns immediately, and the request is processed in the asynchronous context.

Nonblocking I/O

Web containers in application servers normally use a server thread per client request. To develop scalable web applications, you must ensure that threads associated with client requests are never sitting idle waiting for a blocking operation to complete. Asynchronous Processing provides a mechanism to execute application-specific blocking operations in a new thread, returning the thread associated with the request immediately to the container. Even if you use asynchronous processing for all the application-specific blocking operations inside your service methods, threads associated with client requests can be momentarily sitting idle because of input/output considerations.

For example, if a client is submitting a large HTTP POST request over a slow network connection, the server can read the request faster than the client can provide it. Using traditional I/O, the container thread associated with this request would be sometimes sitting idle waiting for the rest of the request.

Jakarta EE provides nonblocking I/O support for servlets and filters when processing requests in asynchronous mode. The following steps summarize how to use nonblocking I/O to process requests and write responses inside service methods.

  1. Put the request in asynchronous mode as described in Asynchronous Processing.

  2. Obtain an input stream and/or an output stream from the request and response objects in the service method.

  3. Assign a read listener to the input stream and/or a write listener to the output stream.

  4. Process the request and the response inside the listener’s callback methods.

Nonblocking I/O Support in jakarta.servlet.ServletOutputStream describe the methods available in the servlet input and output streams for nonblocking I/O support. Listener Interfaces for Nonblocking I/O Support describes the interfaces for read listeners and write listeners.

Nonblocking I/O Support in jakarta.servlet.ServletInputStream
Method Description

void setReadListener(ReadListener rl)

Associates this input stream with a listener object that contains callback methods to read data asynchronously. You provide the listener object as an anonymous class or use another mechanism to pass the input stream to the read listener object.

boolean isReady()

Returns true if data can be read without blocking.

boolean isFinished()

Returns true when all the data has been read.

Nonblocking I/O Support in jakarta.servlet.ServletOutputStream
Method Description

void setWriteListener(WriteListener wl)

Associates this output stream with a listener object that contains callback methods to write data asynchronously. You provide the write listener object as an anonymous class or use another mechanism to pass the output stream to the write listener object.

boolean isReady()

Returns true if data can be written without blocking.

Listener Interfaces for Nonblocking I/O Support
Interface Methods Description

ReadListener

void onDataAvailable()

void onAllDataRead()

void onError(Throwable t)

A ServletInputStream instance calls these methods on its listener when there is data available to read, when all the data has been read, or when there is an error.

WriteListener

void onWritePossible()

void onError(Throwable t)

A ServletOutputStream instance calls these methods on its listener when it is possible to write data without blocking or when there is an error.

Reading a Large HTTP POST Request Using Nonblocking I/O

The code in this section shows how to read a large HTTP POST request inside a servlet by putting the request in asynchronous mode (as described in Asynchronous Processing) and using the nonblocking I/O functionality from Nonblocking I/O Support in jakarta.servlet.ServletInputStream and Listener Interfaces for Nonblocking I/O Support.

@WebServlet(urlPatterns={"/asyncioservlet"}, asyncSupported=true)
public class AsyncIOServlet extends HttpServlet {
   @Override
   public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
                      throws IOException {
      final AsyncContext acontext = request.startAsync();
      final ServletInputStream input = request.getInputStream();

      input.setReadListener(new ReadListener() {
         byte buffer[] = new byte[4*1024];
         StringBuilder sbuilder = new StringBuilder();
         @Override
         public void onDataAvailable() {
            try {
               do {
                  int length = input.read(buffer);
                  sbuilder.append(new String(buffer, 0, length));
               } while(input.isReady());
            } catch (IOException ex) { ... }
         }
         @Override
         public void onAllDataRead() {
            try {
               acontext.getResponse().getWriter()
                                     .write("...the response...");
            } catch (IOException ex) { ... }
            acontext.complete();
         }
         @Override
         public void onError(Throwable t) { ... }
      });
   }
}

This example declares the web servlet with asynchronous support using the @WebServlet annotation parameter asyncSupported=true. The service method first puts the request in asynchronous mode by calling the startAsync() method of the request object, which is required in order to use nonblocking I/O. Then, the service method obtains an input stream associated with the request and assigns a read listener defined as an inner class. The listener reads parts of the request as they become available and then writes some response to the client when it finishes reading the request.

Server Push

Server push is the ability of the server to anticipate what will be needed by the client in advance of the client’s request. It lets the server pre-populate the browser’s cache in advance of the browser asking for the resource to put in the cache.

Server push is the most visible of the improvements in HTTP/2 to appear in the servlet API. All of the new features in HTTP/2, including server push, are aimed at improving the performance of the web browsing experience.

Server push derives its contribution to improved browser performance from the fact that servers know what additional assets (such as images, stylesheets, and scripts) go along with initial requests. For example, servers might know that whenever a browser requests index.html, it will shortly thereafter request header.gif, footer.gif, and style.css. Servers can preemptively start sending the bytes of these assets along with the bytes of the index.html.

To use server push, obtain a reference to a PushBuilder from an HttpServletRequest, edit the builder as desired, then call push(). See the javadoc for the class jakarta.servlet.http.PushBuilder and the method jakarta.servlet.http.HttpServletRequest.newPushBuilder().

Protocol Upgrade Processing

In HTTP/1.1, clients can request to switch to a different protocol on the current connection by using the Upgrade header field. If the server accepts the request to switch to the protocol indicated by the client, it generates an HTTP response with status 101 (switching protocols). After this exchange, the client and the server communicate using the new protocol.

For example, a client can make an HTTP request to switch to the XYZP protocol as follows:

GET /xyzpresource HTTP/1.1
Host: localhost:8080
Accept: text/html
Upgrade: XYZP
Connection: Upgrade
OtherHeaderA: Value

The client can specify parameters for the new protocol using HTTP headers. The server can accept the request and generate a response as follows:

HTTP/1.1 101 Switching Protocols
Upgrade: XYZP
Connection: Upgrade
OtherHeaderB: Value

(XYZP data)

Jakarta EE supports the HTTP protocol upgrade functionality in servlets, as described in Protocol Upgrade Support.

Protocol Upgrade Support
Class or Interface Method

HttpServletRequest

HttpUpgradeHandler upgrade(Class handler)

The upgrade method starts the protocol upgrade processing. This method instantiates a class that implements the HttpUpgradeHandler interface and delegates the connection to it.

You call the upgrade method inside a service method when accepting a request from a client to switch protocols.

HttpUpgradeHandler

void init(WebConnection wc)

The init method is called when the servlet accepts the request to switch protocols. You implement this method and obtain input and output streams from the WebConnection object to implement the new protocol.

HttpUpgradeHandler

void destroy()

The destroy method is called when the client disconnects. You implement this method and free any resources associated with processing the new protocol.

WebConnection

ServletInputStream getInputStream()

The getInputStream method provides access to the input stream of the connection. You can use Nonblocking I/O with the returned stream to implement the new protocol.

WebConnection

ServletOutputStream getOutputStream()

The getOutputStream method provides access to the output stream of the connection. You can use Nonblocking I/O with the returned stream to implement the new protocol.

The following code demonstrates how to accept an HTTP protocol upgrade request from a client:

@WebServlet(urlPatterns={"/xyzpresource"})
public class XYZPUpgradeServlet extends HttpServlet {
   @Override
   public void doGet(HttpServletRequest request,
                     HttpServletResponse response) {
      if ("XYZP".equals(request.getHeader("Upgrade"))) {
         /* Accept upgrade request */
         response.setStatus(101);
         response.setHeader("Upgrade", "XYZP");
         response.setHeader("Connection", "Upgrade");
         response.setHeader("OtherHeaderB", "Value");
         /* Delegate the connection to the upgrade handler */
         XYZPUpgradeHandler = request.upgrade(XYZPUpgradeHandler.class);
         /* (the service method returns immedately) */
      } else {
         /* ... write error response ... */
      }
   }
}

The XYZPUpgradeHandler class handles the connection:

public class XYZPUpgradeHandler implements HttpUpgradeHandler {
   @Override
   public void init(WebConnection wc) {
      ServletInputStream input = wc.getInputStream();
      ServletOutputStream output = wc.getOutputStream();
      /* ... implement XYZP using these streams (protocol-specific) ... */
   }
   @Override
   public void destroy() { ... }
}

The class that implements HttpUpgradeHandler uses the streams from the current connection to communicate with the client using the new protocol. See the Servlet 5.0 specification at https://jakarta.ee/specifications/servlet/5.0 for details on HTTP protocol upgrade support.

HTTP Trailer

HTTP trailer is a collection of a special type of HTTP headers that comes after the response body. The trailer response header allows the sender to include additional fields at the end of chunked messages in order to supply metadata that might be dynamically generated while the message body is sent, such as a message integrity check, digital signature, or post-processing status.

If trailer headers are ready for reading, isTrailerFieldsReady() will return true. Then a servlet can read trailer headers of the HTTP request using the getTrailerFields method of the HttpServletRequest interface. If trailer headers are not ready for reading, isTrailerFieldsReady() returns false and will cause an IllegalStateException.

A servlet can write trailer headers to the response by providing a supplier to the setTrailerFields() method of the HttpServletResponse interface. The following headers and types of headers must not be included in the set of keys in the map passed to setTrailerFields(): Transfer-Encoding, Content-Length, Host, controls and conditional headers, authentication headers, Content-Encoding, Content-Type, Content-Range, and Trailer. When sending response trailers, you must include a regular header, called Trailer, whose value is a comma-separated list of all the keys in the map that is supplied to the setTrailerFields() method. The value of the Trailer header lets the client know what trailers to expect.

The supplier of the trailer headers can be obtained by accessing the getTrailerFields() method of the HttpServletResponse interface.

See the javadoc for getTrailerFields() and isTrailerFieldsReady() in HttpServletRequest, and getTrailerFields() and setTrailerFields() in HttpServletResponse.

The mood Example Application

The mood example application, located in the jakartaee-examples/tutorial/web/servlet/mood/ directory, is a simple example that displays Duke’s moods at different times during the day. The example shows how to develop a simple application by using the @WebServlet, @WebFilter, and @WebListener annotations to create a servlet, a listener, and a filter.

Components of the mood Example Application

The mood example application is comprised of three components: mood.web.MoodServlet, mood.web.TimeOfDayFilter, and mood.web.SimpleServletListener.

MoodServlet, the presentation layer of the application, displays Duke’s mood in a graphic, based on the time of day. The @WebServlet annotation specifies the URL pattern:

@WebServlet("/report")
public class MoodServlet extends HttpServlet {
    ...
}

TimeOfDayFilter sets an initialization parameter indicating that Duke is awake:

@WebFilter(filterName = "TimeOfDayFilter",
urlPatterns = {"/*"},
initParams = {
    @WebInitParam(name = "mood", value = "awake")})
public class TimeOfDayFilter implements Filter {
    ...
}

The filter calls the doFilter method, which contains a switch statement that sets Duke’s mood based on the current time.

SimpleServletListener logs changes in the servlet’s lifecycle. The log entries appear in the server log.

Running the mood Example

You can use either NetBeans IDE or Maven to build, package, deploy, and run the mood example.

To Run the mood Example Using NetBeans IDE

  1. Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).

  2. From the File menu, choose Open Project.

  3. In the Open Project dialog box, navigate to:

    jakartaee-examples/tutorial/web/servlet
  4. Select the mood folder.

  5. Click Open Project.

  6. In the Projects tab, right-click the mood project and select Build.

  7. In a web browser, enter the following URL:

    http://localhost:8080/mood/report

    The URL specifies the context root, followed by the URL pattern.

    A web page appears with the title "Servlet MoodServlet at /mood", a text string describing Duke’s mood, and an illustrative graphic.

To Run the mood Example Using Maven

  1. Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).

  2. In a terminal window, go to:

    jakartaee-examples/tutorial/web/servlet/mood/
  3. Enter the following command to deploy the application:

    mvn install
  4. In a web browser, enter the following URL:

    http://localhost:8080/mood/report

    The URL specifies the context root, followed by the URL pattern.

    A web page appears with the title "Servlet MoodServlet at /mood", a text string describing Duke’s mood, and an illustrative graphic.

The fileupload Example Application

The fileupload example, located in the jakartaee-examples/tutorial/web/servlet/fileupload/ directory, illustrates how to implement and use the file upload feature.

The Duke’s Forest case study provides a more complex example that uploads an image file and stores its content in a database.

Except where expressly provided otherwise, the site, and all content provided on or through the site, are provided on an "as is" and "as available" basis. Oracle expressly disclaims all warranties of any kind, whether express or implied, including, but not limited to, the implied warranties of merchantability, fitness for a particular purpose and non-infringement with respect to the site and all content provided on or through the site. Oracle makes no warranty that: (a) the site or content will meet your requirements; (b) the site will be available on an uninterrupted, timely, secure, or error-free basis; (c) the results that may be obtained from the use of the site or any content provided on or through the site will be accurate or reliable; or (d) the quality of any content purchased or obtained by you on or through the site will meet your expectations.

Any content accessed, downloaded or otherwise obtained on or through the use of the site is used at your own discretion and risk. Oracle shall have no responsibility for any damage to your computer system or loss of data that results from the download or use of content.

Architecture of the fileupload Example Application

The fileupload example application consists of a single servlet and an HTML form that makes a file upload request to the servlet.

This example includes a very simple HTML form with two fields, File and Destination. The input type, file, enables a user to browse the local file system to select the file. When the file is selected, it is sent to the server as a part of a POST request. During this process, two mandatory restrictions are applied to the form with input type file.

  • The enctype attribute must be set to a value of multipart/form-data.

  • Its method must be POST.

When the form is specified in this manner, the entire request is sent to the server in encoded form. The servlet then uses its own means to handle the request to process the incoming file data and extract a file from the stream. The destination is the path to the location where the file will be saved on your computer. Pressing the Upload button at the bottom of the form posts the data to the servlet, which saves the file in the specified destination.

The HTML form in index.html is as follows:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>File Upload</title>
    </head>
    <body>
        <form method="post" action="upload" enctype="multipart/form-data">
            <div>
                <label>File: <input type="file" name="file" /></label>
            </div>
            <div>
                <label>Destination: <input name="destination" value="/tmp" /></label>
            </div>
            <div>
                <input type="submit" name="upload" value="Upload" />
            </div>
        </form>
    </body>
</html>

A POST request method is used when the client needs to send data to the server as part of the request, such as when uploading a file or submitting a completed form. In contrast, a GET request method sends a URL and headers only to the server, whereas POST requests also include a message body. This allows arbitrary length data of any type to be sent to the server. A header field in the POST request usually indicates the message body’s Internet media type.

When submitting a form, the browser streams the content in, combining all parts, with each part representing a field of a form. Parts are named after the input elements and are separated from each other with string delimiters named boundary.

This is what submitted data from the fileupload form looks like, after selecting sample.txt as the file that will be uploaded to the tmp directory on the local file system:

POST /fileupload/upload HTTP/1.1
Host: localhost:8080
Content-Type: multipart/form-data;
boundary=---------------------------263081694432439 Content-Length: 441
-----------------------------263081694432439
Content-Disposition: form-data; name="file"; filename="sample.txt"
Content-Type: text/plain
 Data from sample file
-----------------------------263081694432439
Content-Disposition: form-data; name="destination"
 /tmp
-----------------------------263081694432439
Content-Disposition: form-data; name="upload"
 Upload
-----------------------------263081694432439--

The servlet FileUploadServlet.java begins as follows:

@WebServlet(name = "FileUploadServlet", urlPatterns = {"/upload"})
@MultipartConfig
public class FileUploadServlet extends HttpServlet {
    private final static Logger LOGGER =
            Logger.getLogger(FileUploadServlet.class.getCanonicalName());
}

The @WebServlet annotation uses the urlPatterns property to define servlet mappings.

The @MultipartConfig annotation indicates that the servlet expects requests to be made using the multipart/form-data MIME type.

The processRequest method retrieves the destination and file part from the request, then calls the getFileName method to retrieve the file name from the file part. The method then creates a FileOutputStream and copies the file to the specified destination. The error-handling section of the method catches and handles some of the most common reasons why a file would not be found. The processRequest and getFileName methods look like this:

protected void processRequest(HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException {
    response.setContentType("text/html;charset=UTF-8");

    // Create path components to save the file
    final String path = request.getParameter("destination");
    final Part filePart = request.getPart("file");
    final String fileName = getFileName(filePart);

    OutputStream out = null;
    InputStream filecontent = null;
    final PrintWriter writer = response.getWriter();

    try {
        out = new FileOutputStream(new File(path + File.separator
                + fileName));
        filecontent = filePart.getInputStream();

        int read = 0;
        final byte[] bytes = new byte[1024];

        while ((read = filecontent.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        writer.println("New file " + fileName + " created at " + path);
        LOGGER.log(Level.INFO, "File{0}being uploaded to {1}",
                new Object[]{fileName, path});
    } catch (FileNotFoundException fne) {
        writer.println("You either did not specify a file to upload or are "
                + "trying to upload a file to a protected or nonexistent "
                + "location.");
        writer.println("<br/> ERROR: " + fne.getMessage());

        LOGGER.log(Level.SEVERE, "Problems during file upload. Error: {0}",
                new Object[]{fne.getMessage()});
    } finally {
        if (out != null) {
            out.close();
        }
        if (filecontent != null) {
            filecontent.close();
        }
        if (writer != null) {
            writer.close();
        }
    }
}

private String getFileName(final Part part) {
    final String partHeader = part.getHeader("content-disposition");
    LOGGER.log(Level.INFO, "Part Header = {0}", partHeader);
    for (String content : part.getHeader("content-disposition").split(";")) {
        if (content.trim().startsWith("filename")) {
            return content.substring(
                    content.indexOf('=') + 1).trim().replace("\"", "");
        }
    }
    return null;
}

Running the fileupload Example

You can use either NetBeans IDE or Maven to build, package, deploy, and run the fileupload example.

To Build, Package, and Deploy the fileupload Example Using NetBeans IDE

  1. Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).

  2. From the File menu, choose Open Project.

  3. In the Open Project dialog box, navigate to:

    jakartaee-examples/tutorial/web/servlet
  4. Select the fileupload folder.

  5. Click Open Project.

  6. In the Projects tab, right-click the fileupload project and select Build.

To Build, Package, and Deploy the fileupload Example Using Maven

  1. Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).

  2. In a terminal window, go to:

    jakartaee-examples/tutorial/web/servlet/fileupload/
  3. Enter the following command to deploy the application:

    mvn install

To Run the fileupload Example

  1. In a web browser, enter the following URL:

    http://localhost:8080/fileupload/
  2. On the File Upload page, click Choose File to display a file browser window.

  3. Select a file to upload and click Open.

    The name of the file you selected is displayed in the File field. If you do not select a file, an exception will be thrown.

  4. In the Destination field, type a directory name.

    The directory must have already been created and must also be writable. If you do not enter a directory name or if you enter the name of a nonexistent or protected directory, an exception will be thrown.

  5. Click Upload to upload the file that you selected to the directory that you specified in the Destination field.

    A message reports that the file was created in the directory that you specified.

  6. Go to the directory that you specified in the Destination field and verify that the uploaded file is present.

The dukeetf Example Application

The dukeetf example application, located in the jakartaee-examples/tutorial/web/dukeetf/ directory, demonstrates how to use asynchronous processing in a servlet to provide data updates to web clients. The example resembles a service that provides periodic updates on the price and trading volume of an electronically traded fund (ETF).

Architecture of the dukeetf Example Application

The dukeetf example application consists of a servlet, an enterprise bean, and an HTML page.

  • The servlet puts requests in asynchronous mode, stores them in a queue, and writes the responses when new data for price and trading volume becomes available.

  • The enterprise bean updates the price and volume information once every second.

  • The HTML page uses JavaScript code to make requests to the servlet for new data, parse the response from the servlet, and update the price and volume information without reloading the page.

The dukeetf example application uses a programming model known as long polling. In the traditional HTTP request and response model, the user must make an explicit request (such as clicking a link or submitting a form) to get any new information from the server, and the page has to be reloaded. Long polling provides a mechanism for web applications to push updates to clients using HTTP without the user making an explicit request. The server handles connections asynchronously, and the client uses JavaScript to make new connections. In this model, clients make a new request immediately after receiving new data, and the server keeps the connection open until new data becomes available.

The Servlet

The DukeETFServlet class uses asynchronous processing:

@WebServlet(urlPatterns={"/dukeetf"}, asyncSupported=true)
public class DukeETFServlet extends HttpServlet {
...
}

In the following code, the init method initializes a queue to hold client requests and registers the servlet with the enterprise bean that provides the price and volume updates. The send method gets called once per second by the PriceVolumeBean to send updates and close the connection:

@Override
public void init(ServletConfig config) {
   /* Queue for requests */
   requestQueue = new ConcurrentLinkedQueue<>();
   /* Register with the enterprise bean that provides price/volume updates */
   pvbean.registerServlet(this);
}

/* PriceVolumeBean calls this method every second to send updates */
public void send(double price, int volume) {
   /* Send update to all connected clients */
   for (AsyncContext acontext : requestQueue) {
      try {
         String msg = String.format("%.2f / %d", price, volume);
         PrintWriter writer = acontext.getResponse().getWriter();
         writer.write(msg);
         logger.log(Level.INFO, "Sent: {0}", msg);
         /* Close the connection
          * The client (JavaScript) makes a new one instantly */
         acontext.complete();
      } catch (IOException ex) {
         logger.log(Level.INFO, ex.toString());
      }
   }
}

The service method puts client requests in asynchronous mode and adds a listener to each request. The listener is implemented as an anonymous class that removes the request from the queue when the servlet finishes writing a response or when there is an error. Finally, the service method adds the request to the request queue created in the init method. The service method is the following:

@Override
public void doGet(HttpServletRequest request,
                  HttpServletResponse response) {
   response.setContentType("text/html");
   /* Put request in async mode */
   final AsyncContext acontext = request.startAsync();
   /* Remove from the queue when done */
   acontext.addListener(new AsyncListener() {
      public void onComplete(AsyncEvent ae) throws IOException {
         requestQueue.remove(acontext);
      }
      public void onTimeout(AsyncEvent ae) throws IOException {
         requestQueue.remove(acontext);
      }
      public void onError(AsyncEvent ae) throws IOException {
         requestQueue.remove(acontext);
      }
      public void onStartAsync(AsyncEvent ae) throws IOException {}
   });
   /* Add to the queue */
   requestQueue.add(acontext);
}

The Enterprise Bean

The PriceVolumeBean class is an enterprise bean that uses the timer service from the container to update the price and volume information and call the servlet’s send method once every second:

@Startup
@Singleton
public class PriceVolumeBean {
    /* Use the container's timer service */
    @Resource TimerService tservice;
    private DukeETFServlet servlet;
    ...

    @PostConstruct
    public void init() {
        /* Initialize the EJB and create a timer */
        random = new Random();
        servlet = null;
        tservice.createIntervalTimer(1000, 1000, new TimerConfig());
    }

    public void registerServlet(DukeETFServlet servlet) {
        /* Associate a servlet to send updates to */
        this.servlet = servlet;
    }

    @Timeout
    public void timeout() {
        /* Adjust price and volume and send updates */
        price += 1.0*(random.nextInt(100)-50)/100.0;
        volume += random.nextInt(5000) - 2500;
        if (servlet != null)
            servlet.send(price, volume);
    }
}

See Using the Timer Service in Running the Enterprise Bean Examples for more information on the timer service.

The HTML Page

The HTML page consists of a table and some JavaScript code. The table contains two fields referenced from JavaScript code:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>...</head>
<body onload="makeAjaxRequest();">
  ...
  <table>
    ...
    <td id="price">--.--</td>
    ...
    <td id="volume">--</td>
    ...
  </table>
</body>
</html>

The JavaScript code uses the XMLHttpRequest API, which provides functionality for transferring data between a client and a server. The script makes an asynchronous request to the servlet and designates a callback method. When the server provides a response, the callback method updates the fields in the table and makes a new request. The JavaScript code is the following:

var ajaxRequest;
function updatePage() {
   if (ajaxRequest.readyState === 4) {
      var arraypv = ajaxRequest.responseText.split("/");
      document.getElementById("price").innerHTML = arraypv[0];
      document.getElementById("volume").innerHTML = arraypv[1];
      makeAjaxRequest();
   }
}
function makeAjaxRequest() {
   ajaxRequest = new XMLHttpRequest();
   ajaxRequest.onreadystatechange = updatePage;
   ajaxRequest.open("GET", "http://localhost:8080/dukeetf/dukeetf",
                    true);
   ajaxRequest.send(null);
}

The XMLHttpRequest API is supported by most modern browsers, and it is widely used in Ajax web client development (Asynchronous JavaScript and XML).

See The dukeetf2 Example Application in Jakarta WebSocket for an equivalent version of this example implemented using a WebSocket endpoint.

Running the dukeetf Example Application

This section describes how to run the dukeetf example application using NetBeans IDE and from the command line.

To Run the dukeetf Example Application Using NetBeans IDE

  1. Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).

  2. From the File menu, choose Open Project.

  3. In the Open Project dialog box, navigate to:

    jakartaee-examples/tutorial/web/servlet
  4. Select the dukeetf folder.

  5. Click Open Project.

  6. In the Projects tab, right-click the dukeetf project and select Run.

    This command builds and packages the application into a WAR file (dukeetf.war) located in the target directory, deploys it to the server, and launches a web browser window with the following URL:

    http://localhost:8080/dukeetf/

    Open the same URL in a different web browser to see how both pages get price and volume updates simultaneously.

To Run the dukeetf Example Application Using Maven

  1. Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).

  2. In a terminal window, go to:

    jakartaee-examples/tutorial/web/servlet/dukeetf/
  3. Enter the following command to deploy the application:

    mvn install
  4. Open a web browser window and type the following address:

    http://localhost:8080/dukeetf/

    Open the same URL in a different web browser to see how both pages get price and volume updates simultaneously.

Further Information about Jakarta Servlet Technology

For more information on Jakarta Servlet technology, see the Jakarta Servlet 5.0 specification at https://jakarta.ee/specifications/servlet/5.0/.