The Grand Tour: Servlets

If you’ve followed along, you’ve now got a to-do list database and a couple of classes that view and update it. The next step is to take that functionality to the Web by wrapping it in a Web application.

On the Java platform, there are dozens of ways to do this. The simplest approach is to write a servlet. A servlet is simply a class that has methods that handle requests from Web browsers.

Basic page requests are handled by a doGet() method. Form submissions that use the POST method are handled by a doPost() method. Objects passed to these methods provide access to information about the browser request and allow control over the servlet’s response.

An XML configuration file controls what URLs the servlet is responsible for, and provide any configuration information that the servlet may require.

The Java Web application standard (J2EE) even specifies a directory structure, so that the servlet always knows where to find any class files, configuration files, and additional Web resources (like images and style sheets) that it needs.

So let’s start by putting the stuff we have already into the correct directory structure. Create a new empty directory to work in, then create a subdirectory called WEB-INF. All of the “normal” Web resources (HTML pages, images, style sheets and JavaScript files) will go in the main directory, whereas all the Java stuff (classes, libraries and configuration files) will go in WEB-INF.

Within WEB-INF, create two more subdirectories: classes and lib. WEB-INF/classes will contain all the Java classes for our Web application, while WEB-INF/lib will contain any libraries that those classes may require.

Speaking of required libraries, remember from last time that our ToDoList class requires the JDBC driver for MySQL (MySQL Connector/J) to access the database. For a standalone application, we needed to add the JAR file (mysql-connector-java-version-bin.jar) to the classpath. For a Java Web application, simply drop the file into the WEB-INF/lib directory.

Since we’ll be using the classes we’ve already developed (ToDoList and ToDoItem) in our Web application, we need to put them in the WEB-INF/classes directory. You might as well drop the source files in there along with the compiled classes, just in case you need to make any changes–recompiling will then be a breeze.

So here’s our file and directory structure so far:

/WEB-INF/classes/com/sitepoint/ToDoItem.class
/WEB-INF/classes/com/sitepoint/ToDoItem.java
/WEB-INF/classes/com/sitepoint/ToDoList.class
/WEB-INF/classes/com/sitepoint/ToDoList.java
/WEB-INF/lib/mysql-connector-java-version-bin.jar

Let’s now turn our attention to building the servlet for our application. Remember, servlets are just Java classes, and this one will be com.sitepoint.ToDoServlet:

package com.sitepoint;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class ToDoServlet extends HttpServlet {

As you can see, servlet classes must extend the javax.servlet.HttpServlet class. You can view documentation on this and related classes in the J2EE API Specification (also at JDocs).

Our servlet will use an instance of our ToDoList class, which we want to create when the servlet is first loaded. We can perform initialization tasks like this in the init() method, which the server will call when it loads the servlet:

  private ToDoList toDoList;

  // Initialize global variables
  public void init() throws ServletException {
    toDoList = new ToDoList(getInitParameter("jdbcDriver"),
                            getInitParameter("jdbcConnectionString"));
  }

You may recall that the ToDoList constructor takes the name of the JDBC driver you want to use and a JDBC connection string so that it can connect to your database. Rather than hard-coding these strings into our servlet, this code uses initialization parameters for these values, so that they can be configured without recompiling the servlet. We’ll look more at this later.

You’ll notice that the init() method, like all standard servlet methods, can throw a ServletException. This exception is used to let the server know that something went wrong in the servlet. We’ll see how this works a bit later.

Next up is the doGet() method, which handles normal browser requests. It takes two parameters: an HttpServletRequest that contains detailed information about the browser request, and a HttpServletResponse, that the servlet can use to control its response to the browser:

  public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

Once again, the method is able to throw a ServletException to signal an error in the servlet. The servlet specification also allows this function to throw an IOException, in case there is a problem reading the request, or writing the response.

In response to normal browser requests, we want to display the current to-do list and give the user the ability to add items to the list, and also to delete existing items on the list. First, we must let the HttpServletResponse object know what content type we will be sending back to the browser:

    response.setContentType("text/html");

Now we can ask it for a PrintWriter object that we can use to send HTML code to the browser:

    PrintWriter out = response.getWriter();

The PrintWriter object lets us send HTML code to the browser using its println() method:

    out.println("<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"n" +
                "  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">");
    out.println("<html xmlns="http://www.w3.org/1999/xhtml">");
    out.println("<head>");
    out.println("<title>To-Do List</title>");
    out.println("<meta http-equiv="content-type" " +
                "content="text/html; charset=iso-8859-1" />");

If this doesn’t look very pretty to you, then you’ve spotted the biggest weakness of servlets: the HTML markup is mixed in with the Java logic of your application, making it difficult to read and maintain.

Pressing on, we want to link a CSS style sheet to our page:

    out.println("<link rel="stylesheet" type="text/css" href="" +
                request.getContextPath() + "/styles.css" />");
    out.println("</head>");
    out.println("<body>");

Since we don’t yet know which directory of the Web server our application will be installed in, we don’t know the path where resources like style sheets and images will be available. The above code uses the request object’s getContextPath() method to obtain the path to the root of our application, and from there we can point to the styles.css file, which we’ll create a little later.

Next up, we’ll display our to-do list in an HTML form menu. We can start by grabbing an Iterator from our ToDoList with its getToDoItems() method, and then check if it contains any items with its hasNext() method:

    Iterator toDoItems = toDoList.getToDoItems();
    if (toDoItems.hasNext()) {

We want the form to submit back to the same URL, so that this servlet can handle that request as well, so we use the getRequestURI() method to get it:

      out.println("<form action="" + request.getRequestURI() +
                  "" method="post">");

Since HTML form menus turn into drop-down lists with a size of 1, we want the <select> tag to have a size of at least 2, but we also what it to stretch to accomodate the number of items in our to-do list. We use the Math.max() method choose between 2 and the total number of items in the list, as given by the getItemCount() method of our ToDoList:

      out.println("<select name="deleteid" size="" +
                  Math.max(2, toDoList.getItemCount()) + "">");

We can then loop through the toDoItems with a while loop. Each time through the loop, we pull a ToDoItem out of the Iterator with next(). Since it doesn’t know it contains ToDoItems, we need to cast them to the right class before we can use them:

      while (toDoItems.hasNext()) {
        ToDoItem toDoItem = (ToDoItem) toDoItems.next();

For each item, we want to create a <option> tag with the item's ID as its value. Outputting the ID is no problem, since it's just an integer, but the to-do item itself gets tricky. What if the to-do item contains HTML code, possibly even malicious script code? We don't want to output that stuff and have it interpreted by the browser as part of our site! The solution is to escape any special characters in the value before printing it out.

This is another area where servlets are weak: there is no built-in HTML escaping functionality in the servlet API or in Java in general (at least none that is accessible to us). Thankfully, this is a longstanding issue, and 3rd party classes have been created to do the job. The one I've chosen is developed and distributed by AnyWare. The class is called uk.co.anyware.html.HTMLEscaper, and its source code and license are included in the code archive I've provided below.

To use HTMLEscaper, drop the class (and source code if you want to hold onto it) into the WEB-INF/classes directory and add the required import to the top of the ToDoServlet class:

import uk.co.anyware.html.*;

With the class in hand, we can now safely output the to-do item. We’ll also polish off the rest of the form with a submit button for deleting selected items:

      out.println("</select>");
      out.println("<input type="submit" value="Delete Selected Item" />");
      out.println("</form>");
    }

We’ll finish off the page with a second form, this time for submitting new to-do items:

    out.println("<form action="" + request.getRequestURI() +
                "" method="post">");
    out.println("<input type="text" name="newtodo" />");
    out.println("<input type="submit" value="Add New Item" />");
    out.println("</form>");
  
    out.println("</body>");
    out.println("</html>");
    out.close();
  }

That takes care of displaying the initial page to the user, but we’ve now got two forms submitting back to this same servlet to worry about!

Both of these forms submit using the POST method (method="post"), so we could handle them in the servlet’s doPost() method. But to make the servlet as flexible as possible we’ll also support submissions for adding new to-do items and deleting existing ones via GET requests. To do this, we’ll simply feed POST requests back into our doGet() method:

  public void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    doGet(request, response);
  }

At the top of doGet(), we now need to check for and process our form submissions. Here’s the code for handling new to-do items:

    String newToDo = request.getParameter("newtodo");
    if (newToDo != null) {
      toDoList.addItem(newToDo);
  
      // Redirect to self
      response.sendRedirect(request.getRequestURI());
      return;
    }

The getParameter() method lets us retrieve a submitted value with a given name as a string. If the value isn’t null, we know we have a submission on our hands. We add it to the to-do list using its addItem method, then redirect the browser to the current page with the response’s sendRedirect() method.

If we didn’t redirect the browser, the rest of the doGet() method would indeed display the updated to-do list for the user, but refreshing the page would cause the form submission to be sent again, resulting in a duplicate entry on the to-do list. Redirecting the browser back to the same page forces the browser to think of it as a separate request, and it will therefore not resubmit the form when refreshing the page.

The code for processing deleted items is very similar, except that it must convert the submitted string into an integer for the to-do list’s deleteItem() method:

    String deleteid = request.getParameter("deleteid");
    if (deleteid != null) {
      try {
        toDoList.deleteItem(Integer.parseInt(deleteid));

        // Redirect to self
        response.sendRedirect(request.getRequestURI());
        return;
      }
      catch (NumberFormatException e) {
        throw new ServletException("Bad deleteid value submitted.", e);
      }
    }

The conversion is achieved by the Integer.parseInt() method, but if the value submitted cannot be converted into a number it will throw a NumberFormatException. If this happens, our code catches the exception and throws a ServletException (to which we pass the NumberFormatException as a root cause).

Remember, all standard servlet methods are allowed to throw ServletExceptions to let the server know something has gone wrong. That’s exactly what we’re doing here. When the code throws the ServletException the server running the servlet catches it and displays an appropriate error message in the user’s browser. You can configure most Java servers to control how much technical detail is included in such error pages.

Believe it or not, that’s it for the servlet! You still have to compile it, though, and that’s a little tricky. You see, the HttpServlet class that our servlet extends is included in the libraries distributed with every Java server, and you need that class to be able to compile your subclass.

In Tomcat 5.0, the servlet library is called servlet-api.jar and it can be found in the commonlib directory of your Tomcat installation. You can therefore compile your servlet by including that JAR file in the classpath:

javac -classpath
".;c:Program FilesApache GroupTomcat 5.0commonlibservlet-api.jar" 
com/sitepoint/*.java uk/co/anyware/html/*.java

Here’s what your file and directory structure should look like now:

/WEB-INF/classes/com/sitepoint/ToDoItem.class
/WEB-INF/classes/com/sitepoint/ToDoItem.java
/WEB-INF/classes/com/sitepoint/ToDoList.class
/WEB-INF/classes/com/sitepoint/ToDoList.java
/WEB-INF/classes/com/sitepoint/ToDoServlet.class
/WEB-INF/classes/com/sitepoint/ToDoServlet.java
/WEB-INF/classes/uk/co/anyware/html/HTMLEscaper.class
/WEB-INF/classes/uk/co/anyware/html/HTMLEscaper.java
/WEB-INF/lib/mysql-connector-java-version-bin.jar

We have two things left to add to complete our Java Web application. The first is the styles.css style sheet referenced by our servlet:

body, p, td, th {
  background: #fff;
  color: #000;
  font: medium Verdana, Arial, Helvetica, sans-serif;
}

select {
  width: 100%;
}

Drop this in the main application directory, which also contains the WEB-INF directory.

The other thing we need is the XML configuration file for the application, web.xml. This file is called the deployment descriptor. It must start with a tag like this one:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">

Next up, we provide a name for our Web application:

  <display-name>ToDoServlet</display-name>

Now we’ll configure our servlet. We give it a name and specify its class:

  <servlet>
    <servlet-name>toDo</servlet-name>
    <servlet-class>com.sitepoint.ToDoServlet</servlet-class>

Now, remember the init() method of our servlet used two initialization parameters: one for the JDBC driver class name, and one for the JDBC connection string. We provide values for those parameters here:

    <init-param>
      <description>The JDBC driver class.</description>
      <param-name>jdbcDriver</param-name>
      <param-value>com.mysql.jdbc.Driver</param-value>
    </init-param>
    <init-param>
      <description>The JDBC connection string.</description>
      <param-name>jdbcConnectionString</param-name>
      <param-value>jdbc:mysql://localhost/todo?user=root&password=password</param-value>
    </init-param>
  </servlet>

Finally, we supply a servlet mapping, which sends requests to a given URL to the servlet. For our example, we’ll direct requests for index.html in the root of our application to our servlet:

  <servlet-mapping>
    <servlet-name>toDo</servlet-name>
    <url-pattern>/index.html</url-pattern>
  </servlet-mapping>
</web-app>

That’s it for our application! Here’s the complete list of files:

/styles.css
/WEB-INF/web.xml
/WEB-INF/classes/com/sitepoint/ToDoItem.class
/WEB-INF/classes/com/sitepoint/ToDoItem.java
/WEB-INF/classes/com/sitepoint/ToDoList.class
/WEB-INF/classes/com/sitepoint/ToDoList.java
/WEB-INF/classes/com/sitepoint/ToDoServlet.class
/WEB-INF/classes/com/sitepoint/ToDoServlet.java
/WEB-INF/classes/uk/co/anyware/html/HTMLEscaper.class
/WEB-INF/classes/uk/co/anyware/html/HTMLEscaper.java
/WEB-INF/lib/mysql-connector-java-version-bin.jar

You’re now ready to bundle this application up so that it’s ready to be deployed on a Java-capable server. To do this, you need to use the jar utility included with the JDK. If you’ve added the bin directory of your JDK to your system path, you should be able to run it from the command prompt just by typing jar.

Move to your working directory for the application (the one that contains styles.css and WEB-INF), and type this command:

jar cvf ToDoServlet.war .

This will produce a file called ToDoServlet.war containing your application. It’s actually a ZIP file in disguise.

You now need to deploy this application on your server. Different servers provide different ways of doing this. If you’re using Tomcat, simply copy the ToDoServlet.war file into the webapps directory of your installation. After a few moments, Tomcat will automatically extract the file into a new directory. You can then go and edit the web.xml file to make sure the JDBC connection string contains the correct details for your database server.

Once that’s done, you should be ready to run your application. Load up http://localhost:8080/ToDoServlet/index.html in your browser (assuming Tomcat is running on port 8080 on your local machine). Here’s what you should see:

Have a play with the page. It should work just as you’d expect. Here’s the WAR file (complete with source code if you unzip it):

Download the code (250KB)

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Robert Munteanu

    Congratulations on pulling this one off! I finally got to understand all those cryptic Java web.xml files and deployed this application with (almost) no headaches.

    Thank you

  • andre

    kudos to you for putting up one of the easiest to understand tutorials about java servlets in the web :) you are sorely needed in the java world :)

  • Jake

    Kevin, can you please write a book on java?

  • Tom Reinart

    Once the WAR is unpacked, Tomcat will no longer start:

    [ERROR] Digester – -Parse Error at line 2 column 219: Document root element “web-app”, must match DOCTYPE root “null”.

  • Ken

    Great blog – Very educational. Hopefully I can find some material on Ant, Struts…