Build servlet-based enterprise Web applications

Learn to build better, faster servlets with advanced servlet techniques

The Java servlet architecture provides an excellent framework for server-side processing. Because servlets are written in Java, they can take advantage of Java's memory management and rich set of APIs. Servlets can also run on numerous platforms and HTTP servers without change. Further, the servlet architecture solves the major problem of the CGI architecture: a servlet request spawns a lightweight Java thread, while a CGI request will spawn a complete process. Unless the project has a large budget for high-end Java-based application server projects, servlets offer the best technology to develop server-side components and Web-based applications.

This article, however, is not about the many advantages of the servlet architecture (see Resources for articles covering that); rather, it is about how to use servlets to generate responses efficiently, use less memory in the process, and stream the generated responses back to the client, making response times seem even better. Applying the techniques in this article will allow you to build servlets that can serve more requests and serve them up faster. Based on that foundation, you can build enterprise Web applications that will scale to your needs, run 24x7, and provide the best response times.

To aid my discussion, I use a simple example servlet that counts down from 10 to 1. Each example of the countdown servlet is slightly modified to demonstrate a specific technique. By focusing on the techniques, rather than a fancy example, you'll see how to apply the specific techniques to solve problems in your own servlets.

Note: This article assumes you're familiar with the servlet architecture and have experience writing servlets. See Resources for tutorial information.

Effective servlets

There are two major goals to building servlets for efficient HTML generation. The first goal, especially important for high-volume Web applications, is to carefully control the amount of garbage created during page generation. The more garbage created, the fewer pages generated, and the slower the response time for a request. This bottleneck occurs because the Java VM has to steal cycles to run garbage collection when it should be generating pages. If too much garbage is created, request processing could halt entirely. This is unacceptable for critical Web applications. These problems can be largely avoided using simple ecological techniques: reduce, reuse,and recycle. We'll discuss these techniques in more detail in just a moment.

The second goal of efficient HTML generation is to keep the browser active: the typical Web surfer doesn't have much tolerance for blank gray pages and "waiting for response" messages. The time a Web application spends querying a database or accessing a legacy system is typically the time a user spends staring at a blank page. This can -- and should -- be avoided using servlet response streaming techniques. As we'll see, these techniques dovetail well with the aforementioned ecological techniques, and together they form a foundation for building solid, well-behaved servlets.

Servlet response streaming techniques are also useful for much more than just generating HTML. For servlets, they can be applied when generating XML, JavaScript, GIF or JPEG images, or any other protocol servlets support. In addition, the garbage management techniques we'll cover can be applied to almost any area in Java, especially to applications that must run for an extended period of time.

So let's get started.

Managing garbage

Garbage collection is one of the most attractive features of Java, and servlets can take full advantage of it. Garbage collection still requires a certain amount of overhead, which can be a problem -- especially for server-side applications that generate Web pages dynamically. Server-side applications typically run continuously for extended periods of time. Memory problems that might go unnoticed in client-side applications (like applets or Java GUI applications) could greatly effect the performance of a server-side application in just a few hours of continuous, high-volume operation.

Web page generation requires special consideration, as it uses extensive string manipulation and concatenation. Remember, all Strings in Java are immutable, so it's easy to unintentionally create multiple String objects instead of just one. Creating many unnecessary objects can be considered "ecologically unsound" because additional effort -- in the form of CPU cycles -- is required to clean up the garbage. The more garbage created, the more effort required.

By applying our three simple ecological principles -- reduce the number of objects created, reuse the objects that are created, and recycle objects that are no longer needed -- we can develop servlets that are capable of operating for longer periods of time with less overhead. This process results in Web applications that are more robust and better behaved.

Let's take a look at what each of these principles means in practice.

Reduce

One of the most effective ways to avoid the overhead of garbage collection is simply to reduce the number of objects being created. At first this may sound naive, but thinking carefully about how the objects are being used can lead to simplified and more efficient code. Take the following example:

Example 1

public void doGet ( HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
{
    res.setContentType( "text/html" );
    String html = "<html><head><title>Example 1</title></head>\n";
    html += "<body>\n";
    html += "<h1>Countdown</h1>\n";
    for ( int i = 10; i > 0; i-- ) {
        html += "<h1>" + i + "</h1>";
    }
    html += "<h1>Liftoff</h1>\n";
    html += "</body></html>\n";
    PrintWriter out = res.getWriter();
    out.println( html );
    out.close();
}

Note: This example, while slightly contrived to demonstrate the point, represents what a servlet might look like if ported directly from a Perl CGI script.

The above implementation will create 14 Strings and 13 StringBuffers. Most of these objects are created inside the for loop; each time through the loop a String and a StringBuffer pair are created. A much cleaner way to implement the same method would be to simply write the output directly to the ServletResponse's PrintWriter:

Example 2

public void doGet ( HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
{
    res.setContentType( "text/html" );
    PrintWriter out = res.getWriter();
    out.println( "<html><head><title>Example 2</title></head>" );
    out.println( "<body>" );
    out.println( "<h1>Countdown</h1>" );
    for ( int i = 10; i > 0; i-- ) {
        out.print( "<h1>" );
        out.print( i );
        out.println( "</h1>" );
    }
    out.println( "<h1>Liftoff</h1>" );
    out.println( "</body></html>" );
    out.close();
}

This example generates the same HTML output without creating any objects at all. The difference here is that all output is written directly to the PrintWriter, which does not require creating any intermediate objects. Because fewer objects are created and less memory is used, garbage collection is needed much less frequently. This means more pages can be generated -- and more requests served -- with the same resources.

You can download all the examples in this article from Resources. Each is contained in fully functional servlets, named Example1.java, Example2.java, etc. The actual source code contains comments about the servlet functions, but the examples are intended for use with this article.

Reuse

It isn't always reasonable, or even possible, to simply write all output directly to the PrintWriter provided in the ServletResponse. For example, in situations where a portion of the HTML is going to be generated before the response is served, it makes sense to use some sort of buffering mechanism. As I demonstrated in our first example, the String object can't be used as a buffer. Behind the scenes, the Java compiler really creates a StringBuffer and a String for each line of code that does any string manipulation. This doesn't help reduce the number of objects being created.

A StringBuffer is the best way to manipulate string values. You can create a StringBuffer, use it to concatenate several Strings or other values (including primitives), and then convert it into a single String. Further, the String and StringBuffer will work together, sharing the same memory until the StringBuffer is modified. The following example shows how you can use a StringBuffer instead of String concatenation:

Example 3

public void doGet ( HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
{
    res.setContentType( "text/html" );
    StringBuffer buffer = new StringBuffer( 1024 );
    buffer.append( "<html><head><title>Example 3</title></head>" );
    buffer.append( "<body>" );
    buffer.append( "<h1>Countdown</h1>" );
    for ( int i = 10; i > 0; i-- ) {
        buffer.append( "<h1>" );
        buffer.append( i );
        buffer.append( "</h1>" );
    }
     buffer.append( "<h1>Liftoff</h1>" );       
    buffer.append( "</body></html>" );
    PrintWriter out = res.getWriter();
    out.println( buffer.toString() );
    out.close();
}

In this example, the StringBuffer isn't altered after the call to toString(), so the char array backing the StringBuffer is never copied, which reduces the memory use. The countdown loop in this example demonstrates yet another memory reduction technique. It could have been written

buffer.append( "<h1>" + i + "</h1>" );

but this would have resulted in a String and StringBuffer being created for each pass through the loop.

Recycle

One final note about managing garbage: the garbage collector can't collect garbage it thinks is still being used. Make sure at the end of your page-generation process that all unneeded objects are no longer referenced. If the servlet is completely stateless -- that is, it doesn't need any data to persist between requests -- all objects should be eligible for garbage collection. Keeping references to unneeded objects is similar to a memory leak; memory will grow with each request serviced, but the garbage collector won't be able to reclaim the memory.

Even though Java's garbage collection mechanism greatly reduces the hassle of memory management, there are still some tricks to making garbage collection a little more effective. Create fewer objects, reuse objects, and make sure the garbage collector knows when objects are no longer used. This will help your servlets lead longer and happier lives.

Streaming output

While efficient servlets are important, efficiency isn't our sole concern. Servlets, assuming they aren't trivial examples like the ones we've covered so far, actually have to perform an operation; querying a database using JDBC or accessing an external system using CORBA. The most efficient servlet ever written can't speed up a complex DB2 query on an old mainframe. The end user doesn't care about effective memory use; the end user wants results. And waiting for a query to complete before responding to a request won't get the job done. Obviously, some bottlenecks on the back end can't be avoided, but the user doesn't have to know about it. By streaming output directly to the browser, your end users will be blissfully unaware of the back end bottleneck.

Before jumping into the solution, let's take a closer look at the problem. Here is another look at our countdown servlet, this time with a sleep() added to simulate accessing an external system:

Example 4

public void doGet ( HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
{
    res.setContentType( "text/html" );
    PrintWriter out = res.getWriter();
    out.println( "<html><head><title>Example 4</title></head>" );
    out.println( "<body>" );
    out.println( "<h1>Countdown</h1>" );
    for ( int i = 10; i > 0; i-- ) {
        out.print( "<h1>" );
        out.print( i );
        out.println( "</h1>" );
        try {
            // simulate access to an external system
            Thread.sleep( 1000 );
        }
        catch ( InterruptedException e ) {}
    }
    out.println( "<h1>Liftoff</h1>" );       
    out.println( "</body></html>" );
    out.close();
}

In this example, I've added a 1-second sleep() to the countdown loop. While this works nicely for the specific example, the real purpose of the pause to is to demonstrate the effects of the lag time created by accessing a database or legacy system. Here the effect isn't quite as expected: instead of seeing the numbers counting down with a 1-second interval between each, we get one 10-second pause and then the whole page is displayed at once. This happens because the PrintWriter is buffered and nothing is sent back to the browser until the output stream is close()d at the end of the method.

The Joys of flushing

The technique of writing the generated HTML directly to the PrintWriter has more advantages than just reducing the number of objects created. By writing directly to the PrintWriter with print() or println(), the servlet has the opportunity to send the output all the way back to the browser. All you need is a simple call to flush() (see the Java Servlet Specification in Resources). PrintWriter.flush() will force all the data in the output stream buffer to be sent back to the browser, allowing the browser to display the data, even if the servlet hasn't finished generating the page. The following example adds a flush() to the countdown loop so the browser can display each number in turn:

Example 5

public void doGet ( HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
{
    res.setContentType( "text/html" );
    PrintWriter out = res.getWriter();
    out.println( "<html><head><title>Example 5</title></head>" );
    out.println( "<body>" );
    out.println( "<h1>Countdown</h1>" );
    for ( int i = 10; i > 0; i-- ) {
        out.print( "<h1>" );
        out.print( i );
        out.println( "</h1>" );
        out.flush();        // make sure the browser see the count
        try {
            Thread.sleep( 1000 );
        }
        catch ( InterruptedException e ) {}
    }
    out.println( "<h1>Liftoff</h1>" );       
    out.println( "</body></html>" );
    out.close();
}

The addition of the flush() allows the browser to update as expected. Keep in mind that while the actual processing time hasn't changed, the servlet appears to process the request much faster from the user's point of view. We simply sent the data to the browser as soon as possible, instead of waiting until all processing was completed.

This point is even more important in light of the fact that the browser itself can be multithreaded. If a page contains a number of graphics (in a header, for example), each of those graphics needs a separate HTTP request to be loaded. If the servlet calls flush() after writing the IMG tags for the graphics into the PrintWriter, the browser can start loading the graphics while the servlet is still processing the original request. This reduces the amount of time required to display the complete page in the browser because the separate requests can be handled concurrently.

Refreshing the user

Even with flush() and streaming the response directly to the browser, some interactions with back-end systems are just going to take longer than the user will want to wait. Further, because of different HTTP server implementations, not all servlet engines support PrintWriter.flush(). A simple way to solve both of these problems is to present a "wait page" before beginning a time-consuming server-side operation.

The following Java code demonstrates a simplified way to present a wait page that is refreshed automatically:

Example 6

public void doGet ( HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
{
    res.setContentType( "text/html" );
    HttpSession session = req.getSession( true );
    PrintWriter out = res.getWriter();
    if ( session.getValue( "waitPage" ) == null ) {
        session.putValue( "waitPage", Boolean.TRUE );
        out.println( "<html><head>" );
        out.println( "<title>Please Wait...</title>" );
        // The interesting part:
        // <meta http-equiv="Refresh" content="0">
        out.println( "<meta http-equiv=\"Refresh\" content=\"0\">" );
        out.println( "</head><body>" );
        out.println( "<br><br><br>" );
        out.println( "<center><h1>Your records are being accessed.<br>" );
        out.println( "Please wait.</h1></center>" );
        out.close();
    }
    else {
        session.removeValue( "waitPage" );
        out.println( "<html><head><title>Example 6</title></head>" );
        out.println( "<body>" );
        out.println( "<h1>Countdown</h1>" );
        for ( int i = 10; i > 0; i-- ) {
            out.print( "<h1>" );
            out.print( i );
            out.println( "</h1>" );
            try {
                Thread.sleep( 1000 );
            }
            catch ( InterruptedException e ) {}
        }
        out.println( "<h1>Liftoff</h1>" );       
        out.println( "</body></html>" );
        out.close();
    }
}

This example uses a flag, stored in the HttpSession, to determine whether the wait page has been displayed. If it has not yet been displayed, the wait page is sent to the browser and the flag is set. Besides containing a message to display to the user, the wait page also contains a META tag to force the browser to refresh itself immediately. This tag triggers a second request to the servlet, which is what generates the real page -- in this case, the same code from Example 4.

This technique works because the browser won't refresh its window until it gets a response from the server. The first time the servlet is called, it returns data immediately, updating the browser. The second time the servlet is called, nothing is sent back to the browser until the PrintWriter is close()d. So the browser window isn't updated until the page is complete.

This is an important difference from our first streaming example: in Example 5, the goal was to get as much data as possible, as quickly as possible, back to the browser using flush(). In situations where flush() won't work (for whatever reason), the output of the servlet needs to be buffered until the operation is complete. If data gets back to the browser before the operation is complete, the wait page will be replaced by whatever data is sent. Once you've gone to the trouble of displaying a wait page, it makes more sense to leave it up until the new page is completely ready.

Conclusion

Servlets provide an excellent framework for developing server-side applications, but they don't guarantee that excellent apps will be written. By paying attention to a few simple details and applying ecologically friendly techniques, you'll go a long way toward making your Web applications more robust and usable. Servlet response streaming techniques will keep the end user in the loop, and garbage management techniques will keep your servlets running around the clock. Together they provide a foundation for building full-scale enterprise Web applications.

The small print: "Build servlet-based enterprise Web applications" Article Copyright (c) 1998 Paul Philion. All rights reserved.

Paul Philion is a Sun Certified Java Developer and a principal consultant with the Highland Technology Group. He has spent the last several years leading the development of Web-based applications, using Java and CORBA, for Fortune 50 companies. Paul has a B.S. in Computer Science from Virginia Tech and is a member of the ACM and IEEE. In his free time Paul eats, sleeps, and sometimes takes a walk; otherwise, he is brewing Java.

Learn more about this topic

  • Download the complete source for the examples in this article http://www.javaworld.com/jw-12-1998/servlethtml/jw-12-servlethtml.zip
  • Sun's Servlet Web site http://www.javasoft.com/products/servlet/index.html
  • Servlet Specification version 2.1 http://java.sun.com/products/servlet/2.1/html/servletapiTOC.fm.html
  • Sun's servlet tutorial http://www.javasoft.com/docs/books/tutorial/servlets/index.html
  • Jason Hunter's "Introducing the new Servlet API 2.1" (JavaWorld, December 1998) explains the differences between 2.0 and 2.1, discusses the reasons for the changes, and shows you how to write servlets using the new release http://www.javaworld.com/jw-12-1998/jw-12-servletapi.html
  • Servlets.com is a companion site to Jason Hunter's Java Servlet Programming (O'Reilly, November 1998) http://www.servlets.com
  • O'Reilly provides information detailed information about the book http://www.oreilly.com/catalog/jservlet
  • Alex Chaffee's Servlet FAQ answers common questions about servlets http://www.purpletech.com/java/servlet-faq
  • See JavaWorld's list of articles about server-side Java http://www.javaworld.com/common/jw-ti-ssj.html
  • Check out ServletCentral, a servlet-specific online magazine http://www.servletcentral.com
  • The ServletSource has a number of good servlet tutorials and examples http://www.servletsource.com
Join the discussion
Be the first to comment on this article. Our Commenting Policies