A simple Struts application

In this tutorial we'll see how to build, compile and deploy a simple Struts application. You can download the complete source code (and Windows compile and deploy batch files).

The webapp specs

I'm sure you've seen pages that register a "new user". The user is essentially asked to specify a userid and password (twice). We'll implement just such a page.

I don't mean to disappoint you, but you should know right away that:

  • Not only is this example rather unexciting,
  • It also does not particularly show the advantages of Struts over other approaches (JSP and its ilk).

I've picked this example simply to illustrate the basics of writing, compiling and deploying a Struts webapp. OK. Now we understand each other, on to our specs:

  1. The user is asked to specify a userid and a password, in two separate fields.
  2. The form data is validated to check for a bad userid or password. The form is also checked to ensure that the password keyed in both times are the same.
  3. The data is further checked to ensure that there are no other users with that userid.
  4. If at any stage there is an error, the user is presented with the same start page, with an appropriate error message.
  5. If she manages to run the gauntlet of checks, we'll reward her with a page indicating her successful registration. We'll display the message "You have successfully registered with the user ID xxxxx".

The webapp design

Remember the MVC pattern we discussed in the first tutorial? We'll try to apply that pattern in the design of our application:

Model
Recall that the Model handles data access and persistence. Since our webapp is just to demonstrate Struts basics, we'll satisfy ourselves with specifying an interface for our Model and a dummy implementation to test it out.
View
We'll need just one JSP page to handle user input and to indicate a successful registration. We could've used 2 JSP pages, but I'm using just one JSP page to illustrate some custom Struts tags you can use within JSP.
Controller
As we discussed earlier, this comes in two parts:
Form validation: Checking that the userid and passwords are correctly keyed in. This is done with a class that extends the Struts' ActionForm class.
Business Logic: Checking that the userid is actually available, saving the userid-password pair to a database and displaying the final "you're registered" page. We do all of this from a subclass of the Struts' Action class.

7 steps to Struts heaven

Building your Struts webapp is essentially a 7 step procedure:

  1. Centralize data access into a few Java classes (that is, Models).
  2. Write your JSP pages, taking care to hide your text in Application.properties, to make internationalization easier.
  3. Write subclasses of ActionForm to handle form validation.
  4. Write subclasses of Action to handle business logic.
  5. Tie everything together by writing struts-config.xml.
  6. Make changes (if any) to the default web.xml file.
  7. Deploy your app onto your servlet container.

We'll follow this recipe fairly closely although not in sequence, to make the exposition clearer.

Data Access and Persistence

We have just two pieces of data that we need to store - the userid and password. So our model needs a function like:

public void save(String userid, String pwd)

We also want to retrieve the userid for display in the "you're registered" page. It isn't obvious why such functionality should go into the model, but this is a common pattern in Struts. Accept this for now. This means we need a function like:

public String getUserid()

Building your application around interfaces is usually a good idea. The following interface might specify all the functionality we need:

(listing for RegistrationModel)
package net.thinksquared.struts;

public interface RegistrationModel{
	public static final int OK = 0;
	public static final int ERROR_USER_EXISTS = 1;
	public static final int ERROR_UNKNOWN = 2;

	public int save(String userid,String pwd);
	public String getUserid();
}

The save() function returns ERROR_USER_EXISTS if the userid given by the user has already been taken. We'll implement the interface next. We won't be using a real database, but a HashMap instead:

(listing for DummyRegistrationModel)
package net.thinksquared.struts;

import java.util.HashMap;

public class DummyRegistrationModel implements RegistrationModel{

	private String _userid = null;
	private static HashMap dbase = new HashMap();	

	public int save(String userid, String pwd){

	    try{	
    		//critical section:
    		synchronized(dbase){    
    		    if(dbase.containsKey(userid)) return ERROR_USER_EXISTS;    

    		    dbase.put(userid,pwd);
        	    _userid = userid; 
    		    return OK;			
    	   	}
	    }catch(Exception e){
		  return ERROR_UNKNOWN;
	    }	    	
	}
	public String getUserid(){ return _userid; }
}

I've put the portion of code that checks if the user exists and saves userid-password pair, into a synchronized block because we need these operations to be atomic.

Notice also that we've used synchronized(dbase) and not synchronized(this) because multiple instances of DummyRegistrationModel may access the single shared dbase. Using synchronized(this) would only serialize access to dbase from multiple threads using a single DummyRegistrationModel instance.

Form Validation

We need to check that the given userid and passwords aren't too short or too long. We also have to check that the two passwords keyed in match.

The code listed below does this. Study the code before reading the commentary following it.

(listing for RegistrationForm)
package net.thinksquared.struts;

import javax.servlet.http.*;
import org.apache.struts.action.*;

public final class RegistrationForm extends ActionForm{
        
        private final int MIN_USERID_LENGTH = 5;
        private final int MAX_USERID_LENGTH = 15;
        private final int MIN_PWD_LENGTH = 8;
        private final int MAX_PWD_LENGTH = 20;

        private String _userid = null;
        private String _pwd = null;
        private String _pwd2 = null;

        public String getUserid(){ return _userid; }
        public void   setUserid(String userid){ _userid = userid; }

        public String getPassword(){ return _pwd; }
        public void   setPassword(String pwd){ _pwd = pwd; }

        public String getPassword2(){ return _pwd2; }
        public void   setPassword2(String pwd2){ _pwd2 = pwd2; }
    
        public void reset(ActionMapping mapping, HttpServletRequest request){
            _userid = _pwd = _pwd2 = null;
        }

        public ActionErrors validate(ActionMapping mapping, 
                                     HttpServletRequest request){
        
            ActionErrors errors = new ActionErrors();   
            
            if( _userid == null ){
                errors.add("userid", 
                            new ActionMessage("registration.error.userid.missing"));
            }else if( _userid.length() < MIN_USERID_LENGTH ){
                errors.add("userid", 
                            new ActionMessage("registration.error.userid.short"));
            }else if( _userid.length() > MAX_USERID_LENGTH ){
                errors.add("userid", 
                            new ActionMessage("registration.error.userid.long"));
            }else if( !_pwd.equals(_pwd2)){
                errors.add("password", 
                            new ActionMessage("registration.error.password.mismatch"));
            }else if( _pwd.length() < MIN_PWD_LENGTH ){
                errors.add("password", 
                            new ActionMessage("registration.error.password.short"));
            }else if( _pwd.length() > MAX_PWD_LENGTH ){
                errors.add("password", 
                            new ActionMessage("registration.error.password.long"));
            }
            return errors;
        }
}

When the user fills in the web form and clicks "Submit", the form is processed by Struts, which then populates an instance of RegistrationForm class above. This is done using the setXXX() functions.

Struts knows which ActionForm subclass to use because we'll specify this in the Struts configuration file, struts-config.xml.

Once the RegistrationForm is filled, Struts validates the form by calling the form's validate() function. This behaviour is specified in struts-config.xml.

If RegistrationForm hits errors, then error messages are placed in the JSP page using custom Struts tags. Struts knows where on the web page to place the errors because they're labelled:

errors.add("password", new ActionMessage("registration.error.password.long"));

The "password" label is a reference used by the JSP page indicating what error to display. For example, the following Struts tag on the JSP page:

<html:errors property="password" />

would cause all error messages labelled "password" to be displayed on the web page.

The references to "registration.error.xxxx" are keys in the Application.properties file. Struts knows about this file because it is specified as a "message resource" in the struts-config.xml file.

Lastly, ActionErrors (note the plural) is a container type class that holds instances of ActionMessage. If the container is empty Struts goes on to the next action specified in the struts-config.xml file. In our app this means processing business logic.

Processing Business Logic

Once the form has been validated, the data contained within it needs further processing - we need to save the data, checking to make sure that the userid is avaliable. We have to prompt the user if there's an error and display a "you're registered" page if everything's hunky dory.

This business logic is performed within a subclass of Struts' Action class:

(listing for RegistrationAction)
package net.thinksquared.struts;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.struts.action.*;
import org.apache.struts.util.*;
import org.apache.commons.beanutils.*;

public final class RegistrationAction extends Action{

    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response)
    throws Exception{

        ActionMessages errors = new ActionMessages();

        String userid = (String) PropertyUtils.getSimpleProperty(form,"userid");
        String pwd    = (String) PropertyUtils.getSimpleProperty(form,"password");
    
        RegistrationModel model = new DummyRegistrationModel();
        int err = model.save(userid,pwd);

        if( err == RegistrationModel.ERROR_USER_EXISTS ){
            errors.add("userid", 
                        new ActionMessage("registration.error.userid.exists"));
        }else if( err == RegistrationModel.ERROR_UNKNOWN ){
            errors.add("userid", 
                        new ActionMessage("registration.error.data.unknown"));
        }     

        if(err == RegistrationModel.OK){
            request.setAttribute("registration-successful",model);    
            return mapping.findForward("RegistrationSuccessful");
        }else{
            saveErrors(request,errors);
            return (new ActionForward(mapping.getInput()));
        }        
    }
}

The first thing to note is that all the action goes in the execute() function of RegistrationAction. After Struts validates the form, it calls this function. How does Struts know which subclass of Action to use? You guessed it – it's specified in struts-config.xml.

Unlike the RegistrationForm.validate() which returns an ActionErrors, RegistrationAction.execute() returns an ActionForward. ActionForward is a Struts class that acts as a reference to a JSP page.

We instantiate an ActionForward with a little help from the ActionMapping that is passed into execute() as an argument:

new ActionForward(mapping.getInput())    
mapping.findForward("RegistrationSuccessful")

The first line instantiates a new ActionForward, which is a reference to the "input" JSP page - the page that contains the form we've been passed. This means we're sending the user the same page she submitted (because, of course, there was an error in her input).

The second line simply looks up the ActionForward (read : JSP page) called "RegistrationSuccessful". And where is the mapping between the label "RegistrationSuccessful" and JSP page name stored? In the struts-config.xml, of course.

Similar to RegistrationForm, RegistrationAction makes it known to Struts that there are errors in the user's input by using a container class called ActionMessages, which plays a similar role as the ActionErrors class we encountered earlier.

We have to use a function to "register" the user errors with Struts:

saveErrors(request,errors);

indicates to Struts that it should hold errors against the given request. As before, the errors are displayed in the JSP page ( pointed to by the ActionForward on execute()'s return ) that gets shown next.

If there are no errors, we're home free and can display the "you're registered" page. Take a closer look at the code that does this:

if(err == RegistrationModel.OK){
    request.setAttribute("registration-successful",model);    
    return mapping.findForward("RegistrationSuccessful");
}

We know that the "you're registered" page is referenced by "RegistrationSuccessful" and that it gets sent to the user in the end. However, from our specs that page must display the line:

"You have successfully registered with the user ID xxxxx".

How will the page know what the userid is? Well, that JSP page will contain a tag:

<bean:write name="registration-successful" property="userid" />

When Struts processes this line, it looks up the "registration-successful" attribute in the request object and expects the value of that attribute to be an object with a getUserid() function. Struts determines the name of the getXXX() function by capitalizing the first letter of the property ("userid") and prefixing it with "get". This means that if you use "getUserID()" or "getUserId()" you'll hit an error.

Struts calls this getUserid() function and pastes the value into the web page. So that's what the line

request.setAttribute("registration-successful",model);

does - it sets up the request's attribute "registration-successful" with our model as a value. That solves the mystery of why we had to have the getUserid() function in our RegistrationModel.

The View

In our design we made the decision to use just one JSP page to both receive data from the user and to display the "you're registered page". The JSP for this is:

(listing for index.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="/tags/struts-logic" prefix="logic" %>

<html:html>
    <head>
        <title><bean:message key="registration.jsp.title"/></title>
        <style type="text/css">
            .error { color: red; font-weight:bold }
        </style>
    </head>    
    <body>
        
        <h1><bean:message key="registration.jsp.heading"/></h1>
    
        <logic:present name="registration-successful" scope="request">
            <h2>    
                <bean:message key="registration.jsp.registration-successful"/>   
                <bean:write name="registration-successful" property="userid" />
            </h2>
        </logic:present>

        <logic:notPresent name="registration-successful" scope="request">
            <html:form action="Registration.do" focus="userid">

                <dl>
                    <dt><bean:message key="registration.jsp.prompt.userid"/></dt>            
                    <dd>
                        <html:text property="userid" size="20" />
                        <span class="error"><html:errors property="userid" /></span>
                    </dd>

                    <dt><bean:message key="registration.jsp.prompt.password"/></dt>            
                    <dd>
                        <html:text property="password" size="20" />
                        <span class="error"><html:errors property="password" /></span>    
                    </dd>

                    <dt><bean:message key="registration.jsp.prompt.password2"/></dt>            
                    <dd><html:text property="password2" size="20" /></dd>

                    <dt><html:submit property="submit" value="Submit"/>
                        <html:reset/>
                    </dt>            
                </dl>
                        
            </html:form>
        </logic:notPresent>
    
    </body>
</html:html>

The first line describes the type of output Struts should produce. The next 3 lines:

<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-logic" prefix="logic" %>

are analogous to "import" statements in Java - they are Struts's custom tags that are used to extend JSP's vocabulary. The URI's are mapped to real file paths in JSP configuration file, web.xml.

These Struts custom tags are commands to perform substitutions, based on data contained in one of two places:

  1. The ActionErrors class that contains errors for that page. We've seen how the RegistrationForm and RegistrationAction classes signal errors with this container class.
  2. Struts effectively backs each instantiation of a JSP page with server-side objects that store state for that page. This is a set of attributes that may be read or written, both by server-side objects like Action or ActionForm and by the JSP page itself.

We can see examples of (1) when the JSP looks for errors to display:

<html:errors property="password" />

This displays errors that have been saved with the property "password". An example of (2) is:

<bean:message key="registration.jsp.heading"/>

which displays the the value of the key "registration.jsp.heading". This key is in the Application.properties file, and Struts loads it up as soon as the webapp is deployed. Struts knows it has to do this because it's specified in the struts-config.xml file.

Another example of (2) is the logic tag:

<logic:present name="registration-successful" scope="request">
   <h2>    
     <bean:message key="registration.jsp.registration-successful"/>   
     <bean:write name="registration-successful" property="userid" />
   </h2>
</logic:present>

The logic tag causes Struts to check if the attribute called "registration-successful" is present. If so, Struts process the enclosed content.

Such attributes have scope. In this code:

<logic:present name="registration-successful" scope="request">

the scope is limited to the request itself. Where was this attribute created and how did it come to have this narrow scope? We see the answer in RegistrationAction:

request.setAttribute("registration-successful",model);

One other thing about attributes - they may either be simple or complex. Simple attributes have just Strings as values and are written onto the web page with bean:message.

Complex attributes are really objects that have getXXX() functions. To display the value of a complex attribute, we use bean.write:

<bean:write name="registration-successful" property="userid" />

When Struts looks up the value of "registration-successful" it interprets the value of this key as an Object, and calls a getUserid() function on it, since that was specified by property="userid".

Lastly, you can see that the form is submitted to "Registration.do". Struts knows what this means because we've specified the mapping to a server-side object (in our case, RegistrationForm) in struts-config.xml.

The properties file

Our single JSP file has no text in it. All text is referenced through property keys, stored in a properties file ( we can specify more than one ) that Struts loads at startup. For simple applications, this is useful and simplifies internationalization.

We've called this file Application.properties:

(listing for Application.properties)
;----------------------------------------------------
;---------- file: Application.properties   ----------
;---------- Resources for registration.jsp ----------
;----------------------------------------------------

registration.jsp.title=Register New User
registration.jsp.heading=Register New User 
registration.jsp.prompt.userid=Please enter a userid (between 5 to 15 characters):
registration.jsp.prompt.password=Please enter a password (between 8 to 20 characters):
registration.jsp.prompt.password2=Please re-enter the password:
registration.jsp.registration-successful=You are successfully registered with the userid 

;---------- validation and error messages------------
registration.error.userid.missing=Please key in a userid.
registration.error.userid.exists=Sorry, this userid is taken! Please try another.
registration.error.userid.short=Your userid is too short.
registration.error.userid.long=Your userid is too long.

registration.error.password.short=Your password is too short.
registration.error.password.long=Your password is too long.
registration.error.password.mismatch=The passwords you keyed in don't match. Please try again.

registration.error.data.unknown=Error saving userid and password.

I hope you can see how by factoring out text from JSP we can carry out internationalization painlessly - no need for multiple JSPs – just create a new properties file. We'll tackle internationalization in a later tutorial.

Putting it all together

Finally! We're now in a position to put together our application. Let's think of the things we need to specify:

  1. Where the start page of our webapp is.
  2. Where Struts can locate the custom Struts tags we've used in our JSP file.
  3. Where Struts should locate our Application.properties file.
  4. What to do when the form is submitted to "Registration.do"
  5. What ActionForm subclass should be populated by data from our JSP page.
  6. If the form needs validation.
  7. Where to process business logic
  8. Where to find the "RegistrationSuccessful" page.

That's quite a shopping list! You probably know by now that there are just 2 configuration files involved:

web.xml
which is required by the servlet container and which we'll hardly change from app to app.
struts-config.xml
which is pretty much unique to each app.

The first two questions are handled by web.xml:

(listing for web.xml)
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
  "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">

<web-app>
  <display-name>Struts Blank Application</display-name>
  
  <!-- Standard Action Servlet Configuration (with debugging) -->
  <servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <init-param>
      <param-name>debug</param-name>
      <param-value>2</param-value>
    </init-param>
    <init-param>
      <param-name>detail</param-name>
      <param-value>2</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
  </servlet>

  <!-- Standard Action Servlet Mapping -->
  <servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>

  <!-- The Usual Welcome File List -->
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <!-- Struts Tag Library Descriptors -->
  <taglib>
    <taglib-uri>/tags/struts-bean</taglib-uri>
    <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
  </taglib>

  <taglib>
    <taglib-uri>/tags/struts-html</taglib-uri>
    <taglib-location>/WEB-INF/struts-html.tld</taglib-location>
  </taglib>

  <taglib>
    <taglib-uri>/tags/struts-logic</taglib-uri>
    <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
  </taglib>
</web-app>

As you can see, the "welcome file list" tag specifies the start page of our webapp:

  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

The custom tags's URIs are specified in a "taglib" sections:

  <taglib>
    <taglib-uri>/tags/struts-html</taglib-uri>
    <taglib-location>/WEB-INF/struts-html.tld</taglib-location>
  </taglib>

When we later deploy the webapp, we'll have to copy the .tld files in our development "lib" directory into our application's WEB-INF directory.

So, it stands to reason that if we wanted to change the name of our start page, or if we wanted to add more custom tags, we'd have to edit the web.xml file.

The rest of our shopping list revolves around struts-config.xml:

(listing for struts-config.xml)
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
    "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>

    <form-beans>
        <form-bean name="RegistrationForm" 
                      type="net.thinksquared.struts.RegistrationForm"/>
    </form-beans>
    <action-mappings>
        <action path="/Registration"
                type="net.thinksquared.struts.RegistrationAction"
                name="RegistrationForm"
                scope="request"
                validate="true"
                input="/index.jsp">

            <forward name="RegistrationSuccessful" path="/index.jsp"/>
        </action>    
    </action-mappings>
    <message-resources parameter="Application"/>
</struts-config>

Struts finds the Application.properties file from the "message-resources" tag:

<message-resources parameter="Application"/>

Property files are usually located in the classpath, along with the .class files of your webapp, so Struts assumes it's somewhere on the classpath. Struts always assumes that the classpath of your webapp is /WEB-INF/classes/. So, for the message resource above, Struts expects to find a file called Application.properties in /WEB-INF/classes/. If you've put the properties further down the package, say /WEB-INF/classes/net/thinksquared/struts/, then the appropriate tag would be:

<message-resources parameter="net.thinksquared.struts.Application"/>

We tell Struts about our form bean, RegistrationForm within the form-beans tag:

<form-beans>
    <form-bean name="RegistrationForm" 
                  type="net.thinksquared.struts.RegistrationForm"/>
</form-beans>

The form-beans section is where we declare all the ActionForm subclasses that we use. The idea behind this is that form validation may be used by more than one JSP page. By declaring our form beans, and giving them names we make them accessible throughout struts-config.xml. In the code above, we've referred to net.thinksquared.struts.RegistrationForm by the name RegistrationForm.

Lastly, we specify the flow of control for each page in the "action-mappings" tag:

<action path="/Registration"
           type="net.thinksquared.struts.RegistrationAction"
           name="RegistrationForm"
           scope="request"
           validate="true"
           input="/index.jsp">

     <forward name="RegistrationSuccessful" path="/index.jsp"/>
</action>

In this code fragment above, forms in /index.jsp submitted to Registration(.do) will be processed by an Action subclass named net.thinksquared.struts.RegistrationAction , but only after the data has been validated by the form bean called RegistrationForm.

Further, the flow of control ends at just one destination: the JSP page referred to as "RegistrationSuccessful", which happens to be the same /index.jsp file we started out from:

<forward name="RegistrationSuccessful" path="/index.jsp"/>

Recall that the name "RegistrationSuccessful" was used in RegistrationAction:

return mapping.findForward("RegistrationSuccessful");

The "mapping" object represents action mappings found in struts-config.xml.

Compiling

Before you can compile the webapp, you need to put the Struts jar files in your development "lib" directory and the jar file containing the javax.servlet package (usually servlet-api.jar) in your classpath.

Compile with the -d option, specifying the development "deploy/WEB-INF/classes/" directory as the destination. I'd suggest compiling with the -deprecation option too, because Struts is constantly developing, and you might want to make your app compatible with the latest Struts release. For example, for our current app, the following suffices:

javac -deprecation 
      -d deploy/WEB-INF/classes 
      -classpath lib/struts.jar;lib/commons-beanutils.jar;lib/servlet-api.jar 
       ./src/*.java

The compilation process ends with a WAR (Web Archive file), which is just a Jar file but with a .war extension. To create this WAR file, you need to ensure that your compilation script puts things in the right place. I'll assume our build area is called "deploy", on the main development folder for our webapp:

  1. The JSP file, index.jsp goes into deploy/ (this is specified in struts-config.xml)
  2. struts-config.xml and web.xml go into deploy/WEB-INF/
  3. All the *.tld files you find in your "lib" directory go into deploy/WEB-INF/ (this directory is specified in your web.xml)
  4. The Struts jar files from your "lib" directory and any other Jar files you might use have to be dumped into deploy/WEB-INF/lib/
  5. your Java class files go into deploy/WEB-INF/classes/xxx where xxx is the package structure of your application. For example, if you have a class net.thinksquared.struts.RegistrationForm, then RegistrationForm.class should be in the directory:
    deploy/WEB-INF/classes/net/thinksquared/struts/
  6. The Application.properties file should go into deploy/WEB-INF/classes/ (we specified this in struts-config.xml)

Once your script has done this, go into the deploy directory and issue the jar command (note the period at the end):

jar cvf registration.war .

and you'll have a registration.war file in the deploy directory ready for use. It is important to note that you can not issue: "jar cvf registration.war ./deploy/" because this would make "deploy" the root directory in your WAR file, causing your servlet container to give you "file not found" errors.

Deploying

Deploying the WAR file in most servlet containers is simple:

  1. Stop the servlet container (in Tomcat, use the "shutdown" batch file)
  2. Delete "webapps/<your app name>/"folder, if any.
  3. Place the WAR file in the webapps directory.
  4. Start the servlet container.

You may find it helpful to automate these steps with a batch file.

In Summary

We've learnt how to write and deploy a simple Struts application. I strongly recommend running the webapp yourself.