Common Web development is a sometimes sweet but often bitter experience. Java Web developers go to great lengths to provide a stateless model, but the resulting performance and ease of deployment make the efforts worthwhile. In this article, I talk about a radically different approach to Web development called the continuation server. By offering a stateful programming model without giving up the scalability inherent in statelessness, continuation servers make Web application development much easier.
When the entire industry moved to Web development back in the mid-1990s, software developers were ecstatic. The client-server applications we'd been building were more user friendly than the terminal-plus-host alternatives, but several problems had plagued us:
- Performance was often poor. One of the nice things about terminal-based development is that the communication costs are constrained by the programming model. Once those constraints were gone, we lacked the bandwidth, tools, or skill to build simple distributed applications.
- The applications were not portable. Most client-server development environments dictated your hardware and software environments.
- Applications were hard to deploy. You had to manage potentially thousands of clients independently.
- The biggest costs were hidden. Deployment turned out to be the most serious constraint because post-production costs skyrocketed.
Client-server computing still moved forward. Often, companies made financial decisions based on the lower cost of software and hardware, only to see management costs explode after production. In 1995, the client-server model needed dramatic improvements -- and was about to get them.
Web development exploded in the mid-1990s. With the emergence of the Java language, you could build distributed Web applications and simultaneously solve the most serious client-server problems with new capabilities:
- Constrained communications. The request/response Web model has all of the characteristics of the terminal-based development. The user types into a form, makes a request, and gets a response. The chatty communications between the client and the server went away and performance improved.
- Shared-nothing architectures. Servlet-based programming could be stateless. That meant that one servlet could serve any client, and a fixed pool of servlets could serve many more users. You didn't need to reserve a servlet for each user. Performance improved even more.
- Common standards on the client. Suddenly, by deploying a common browser to all clients, you could build one interface and reach virtually all clients. Supporting many browser clients has since proven problematic but nowhere near as difficult as supporting native user-interface libraries. Many portability problems went away.
- A better deployment model. By going to a browser as a common client, distribution got much simpler. A company could deploy an application to a few Internet servers and share them across the enterprise. The networking architecture could often share requests across multiple servers, so increasing capacity could be as easy as dropping in another server. The client-side deployment was as easy as making sure the correct browser was on a common client. Management got much simpler.
Performance, scalability, manageability, and portability all got much better, and the Internet revolution kicked into high gear. But you had to make some significant compromises.
One little word -- stateless -- shifts a tremendous burden from the system to the developer. The payoff can't be disputed: you get fantastic scalability because you don't need to maintain one service, or servlet, per user. But the onus of managing state shifts from the programming language to the developer. Today, you can think of Web development as a series of stateless requests, as in Figure 1:
Figure 1. Web applications are made of request/response pairs

With this model, the browser is firmly in control. The application can react only to requests made by the browser. That model works if requests are small and independent, but it fails miserably for Web applications that drive multipart applications. The most common example is online shopping. Click a Submit button twice and you've often unintentionally ordered two of the same item. Leave and come back two weeks later, and you might find the same items in your cart, without expecting that behavior.
You typically provide a stateful experience to your users by putting all data related to a conversation into a session and marking that user session at the client with a cookie, hidden fields, or URL variables. For each new request, you're forced to do the following in this order:
- Get the user's identifier from the client
- Restore the conversation state from the session
- Process the user's request
- Build the response
- Store the conversation state in the session
- Send the response to the user
I've oversimplified the problem because using a stateless model to simulate stateful applications can cause even more serious problems. The most critical question is as old as Web development: How do you handle the Back button?
It may shock you to learn that you can do Web development and get a stateless experience for the user with a stateful model for the programmer. In fact, in Hackers and Painters (see Resources), Paul Graham talks about using the underlying approach as early as 1995 at ViaWeb. The approach uses a programming control structure called the continuation.
The basic idea is this: You can let your programming framework load your application's state before a request and save your application's state after each request. I'll start with a look at a continuation in the Ruby programming language.
If you want to code along, install Ruby and then type irb. Define a method called loop by typing the commands after the > character, as shown in Listing 1:
Listing 1. Creating the loop method
irb(main):001:0> def loop(interrupt)
irb(main):002:1> for i in 1..10
irb(main):003:2> puts "Value of i: #{i}"
irb(main):004:2> callcc {|c| return c} if i == interrupt
irb(main):005:2> end
irb(main):006:1> end
=> nil
|
The loop method takes a single parameter called interrupt. You start a for loop from 1 to i, print the value of i, and then ... whoa. The cryptic callcc statement means call with continuation. Think of a continuation as the state of a program at a frozen point in time. Ruby calls the code block in braces, passing in a continuation object. The code in braces is a closure and is simply a block of code that's passed to callcc. The end result is that callcc captures the state of execution and stores the result in c. You can now call this method and interrupt execution at any point of the loop, capturing the state of the program. At a later point, you can resume.
Now execute the method a couple of times, as shown in Listing 2:
Listing 2. Executing the loop method
irb(main):007:0> cont = loop 5 Value of i: 1 Value of i: 2 Value of i: 3 Value of i: 4 Value of i: 5 => #<Continuation:0x2b5a358> irb(main):008:0> cont.call Value of i: 6 Value of i: 7 Value of i: 8 Value of i: 9 Value of i: 10 => 1 10 irb(main):009:0> cont = loop 8 Value of i: 1 Value of i: 2 Value of i: 3 Value of i: 4 Value of i: 5 Value of i: 6 Value of i: 7 Value of i: 8 => #<Continuation:0x2b562f0> irb(main):010:0> cont.call Value of i: 9 Value of i: 10 |
Each time you implement the call, the continuation picks up where execution left off. So a Web development framework that uses continuations can capture a continuation after processing each request and store it in the session with an identifier. The framework can then restore the continuation from the session before each new request, using the same approach that you used to store user data.
The continuation-server approach in many ways lets you have your cake and eat it too -- a stateful programming model and a user experience with stateless performance. Drilling down, these are the approach's advantages:
- It assures statelessness between requests. The framework can identify individual continuations within a URL and store that continuation in the session.
- It gives you a stateful programming model. The framework can restore any continuation at any time. If the user submits a form a second time, the continuation can pick up processing at an earlier point in time.
- You can invalidate continuations based on business rules, so it's easy to prevent a form from being submitted twice.
- You get Back button support thrown in for free. Because you literally have the execution state at any point in time, the framework can simply restore the appropriate continuation if the user ever hits the Back button.
- Threading becomes easy because no resources are shared. No resource contention means most threading problems go away.
Continuations dramatically simplify the Web development model. With continuations, you can think of a Web application as an application with a series of requests to provide more user information. Figure 2 shows the revised application flow. After the user starts an application, the Web server is in control. Instead of fielding arbitrary requests in an arbitrary order, an application becomes a unified, directed conversation with a user.
Figure 2. Continuations allow a more natural application flow

Like many higher-order abstractions, continuations have drawbacks. Those disadvantages will transfer to the overall approach that depends on them. Continuation servers must solve these common problems:
- Continuations can be expensive. Making it easier to put data into a session means more people will put more data into a session. A framework designer can lessen the expense by using an approach called partial continuations, where the framework saves only the application-specific portion of the continuation.
- Ugly URLs. Most continuation servers append an ugly identifier to a continuation.
- A changing user paradigm. If you use continuation servers pervasively, the user experience will change. The Back button will actually work, and that experience will disorient some users. For example, if you click on the Back button after placing something into your shopping cart, would you expect the application to take something out of your cart?
Overall, I believe that continuations represent a significant step forward, and you're likely to see pervasive implementations of continuation servers in the near future. Now let's take a quick look at some implementations in other languages, and then I'll show you what's going on closer to home.
Implementations in other languages
It turns out that several languages have continuations-based approaches (see Resources for links to more information about all of them). The most common are in Lisp, Ruby, and -- most of all -- Smalltalk.
- Seaside, the most popular continuation server, is implemented in Smalltalk by Avi Bryant. If you want to understand a radically different and intensely productive Web development framework, get to know Seaside. The experience will change the way that you approach Java programming.
- The Iowa framework in Ruby was also created by Avi Bryant, with Web Objects as an inspiration. It's now used and maintained in Ruby, with a different supporting cast.
- The Wee framework is another Ruby framework that has many of the characteristics of a continuation server but doesn't use native language continuations.
- ViaWeb used continuations with Lisp to get a productivity edge over its competition in 1995.
- Continuity is a Perl-based continuation server. Perl 6 will support continuations natively.
There are a few others I haven't listed here. Right now, you'd expect most of the implementations of continuation servers to be in languages that support continuations, and these are not mainstream languages. But that's not a fully accurate picture of what's happening.
Continuation servers in the Java language
Java developers are increasingly seeing the power of the continuation-based approach to building Web frameworks, but the Java language does not support native continuations. If your language doesn't support continuations, you have a limited number of options: simulate continuations, add continuations to your existing language, or pick a new language. Different Java frameworks (see Resources) use each one of these approaches:
- Simulate continuations. Some Java frameworks use alternative methods to capture the state of execution. Lakeshore uses threads, and Spring Web Flow uses a state machine. (You'll see more Web Flow later in this article.)
- Pick a different language. The Java virtual machine has alternate languages that can sit on top. Cocoon 2 takes this approach using Rhino, a virtual machine that supports JavaScript, but also Java.
- Implement continuations. The Jetty 6 servlet container, RIFE, and WebWork use this approach.
Simulating native continuations
You don't have to use continuations to capture execution state. Suspended threads and state machines can both capture execution state nicely. The threading approach simply freezes and stores an existing thread. This approach is a little more limited, because it does not give you stateless performance -- you are in fact using a separate thread per user.
But the state machine is a nice approach to continuations. A state machine is a program with well-defined transitions from state to state. By far, the most popular Java framework with a heavy continuation-server influence is Spring's Web Flow. Web Flow models the navigation between the pages of a user interface as a state machine. Web Flow can model flows as Java objects, but usually you'll use XML to describe a flow. Consider the flow representation in Listing 3 from the Web Flow tutorial (see Resources):
Listing 3. Web Flow flow representation
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE webflow PUBLIC "-//SPRING//DTD WEBFLOW//EN"
"http://www.springframework.org/dtd/spring-webflow.dtd">
<webflow id="myFlow" start-state="displayForm">
<view-state id="displayForm" view="form">
<entry>
<action bean="myFlowAction" method="setupForm"/>
</entry>
<transition on="submit" to="processSubmit">
<action bean="myFlowAction" method="bindAndValidate"/>
</transition>
</view-state>
<action-state id="processSubmit">
<action bean="myFlowAction"/>
<transition on="success" to="finish"/>
</action-state>
<end-state id="finish" view="success"/>
</webflow>
|
This flow has three core states: displayForm, processSubmit, and finish. The flow defines actions that will make the machine transition from one state to the next. For example, a submit will transition the state from displayForm to processSubmit. Web Flow works above the typical model-view-controller level. You can use many different view technologies to display your forms.
As you transition from one state to the next, the framework captures the current state, with all instance variables, automatically. You get the same Back button support and stateful programming model that other continuation servers provide. This approach does not use native continuations, but you get many of the advantages of continuation servers, and some other advantages as well:
- Persistence. Although you can't capture the whole call stack, you can capture the current execution state and even persist the current state.
- Long-lived flows such as workflow. Because the state isn't dependent on a given call stack, the approach is more stable for things like long-duration workflows.
- Tools. It's relatively easy to build tools for XML.
Cocoon uses Rhino, the JavaScript virtual machine that plugs into the JVM. Cocoon controllers use a modified version of Rhino to express continuations, so Cocoon lets you express your controller logic in Java or JavaScript. Consider the application in Listing 4, from the Cocoon tutorial (see Resources):
Listing 4. Using Cocoon
try {
if (operator == "plus")
cocoon.sendPage("result.html", {result: a + b});
else if (operator == "minus")
cocoon.sendPage("result.html", {result: a - b});
else if (operator == "multiply")
cocoon.sendPage("result.html", {result: a * b});
else if (operator == "divide")
cocoon.sendPage("result.html", {result: a / b});
else
cocoon.sendPage("invalidOperator.html", {operator: operator});
}
catch (e) {
cocoon.sendPage("error.html",
{message: "Operation failed: " + e.toString()});
}
|
Notice the sendPage method in Listing 4. This method sends a page back to the user. You don't see any of the normal tedious code to support the Back button, or to save user data to the session -- that's all encapsulated into Cocoon's framework.
The RIFE framework implements its own continuations, and the WebWork framework uses the RIFE continuations implementation. Jetty 6 also includes a continuation implementation in Java. Listing 6 shows an example from RIFE's tutorial, to play a guess-a-number game:
Listing 5. A RIFE example
while (mGuess != answer) {
print(template);
pause();
guesses++;
if (answer < mGuess) {
template.setBlock("indication", "lower");
} else if (answer > mGuess).{
template.setBlock("indication", "higher");
}
}
|
In this example, the method called pause() captures the continuation and sends the template back to the user for action. RIFE makes continuations simple and accessible to the everyday Web developer.
You can see that continuations represent a real advancement in Web development frameworks. You can be much more productive with this approach. And because you express your Web application as straightforward Java code instead of as hundreds of disconnected servlets, your applications are much easier to read and maintain.
New advances in Web development are rapidly making the continuations approach much more important. Rather than fetching whole Web pages with the traditional request/response models, Ajax applications can asynchronously fetch a small part of a Web page and weave the results into an existing page. But Ajax applications tend to force an application to maintain a connection to a user for long periods of time to keep applications responsive and keep state tracking easy to code. That practice defeats the purpose of stateless programming because you do need to hold resources for each connected user. With continuations, you can keep the state in a continuation and restore the state on demand.
In the near future, hardware improvements will make the added resource consumption of continuations less critical. Without a major overhaul, Web development frameworks will still be too complicated. Ajax threatens to complicate Web development even more. These factors are all converging to drive the acceptance of continuation servers. In two years, most new Web development will use some continuation server or some simulation of continuations.
Next time, I'll talk about domain-specific languages and their role in Ruby on Rails. I'll then bring the ideas home and show you what's happening for domain-specific languages in Java programming.
Learn
-
"Java Web Server: Jetty 6.0 Continuations for Ajax Architectures": Ben Galbraith talks about the forces in Ajax influencing a continuations-based approach.
-
"Secrets of lightweight development success, Part 8: Seaside" (Bruce Tate, developerWorks, November 2005): Get an introduction to the most popular and important continuation server.
- Hackers & Painters by Paul Graham (O'Reilly, 2004): One of the many essays in this book recounts the experiences of a startup using continuations.
- Advanced Control Flow - Continuations: This portion of the user documentation for the popular Apache Cocoon Project explores continuations in Cocoon.
- What Is Iowa: Check out a tutorial on Iowa, a continuation server in Ruby.
- RIFE/continuations: RIFE/continuations is a subproject of RIFE that aims to make its support for continuations in pure Java available as a general-purpose library.
- Experimental continuations in WebWork: The WebWork Java Web-application development framework uses RIFE's continuations for its new continuations implementation.
- Spring Web Flow Quick Start: This tutorial helps you get started quickly with Spring Web Flow 1.0.
- Beyond Java (O'Reilly, 2005): Read Bruce Tate's book about Java's rise and plateau and technologies that could challenge Java in some niches.
- The Java technology zone: Hundreds of articles about every aspect of Java programming.
Get products and technologies
- RIFE: RIFE is an innovative framework that uses continuations and many other techniques popular in dynamic languages.
- Spring's Web Flow: Spring Web Flow is a generic flow engine focused on the definition and execution of page flow within a Web application.
- Lakeshore: Lakeshore is a Java-based Web component framework heavily influenced by Seaside 2 and the Borges Ruby framework.
- Jetty 6: Jetty is a servlet container in Java with continuations support.
- Seaside: Seaside is the most popular and influential continuation server.
- Wee: Wee is another Ruby continuations framework.
- Continuity: Continuity is a continuation-based Web-programming framework for Perl.
Discuss
- developerWorks blogs: Get involved in the developerWorks community.

Bruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released Spring: A Developer's Notebook. He spent 13 years at IBM and is now the founder of the J2Life, LLC, consultancy, where he specializes in lightweight development strategies and architectures based on Java technology and Ruby.





