Home | Articles

Writing Application Clients

Sometimes enterprise applications use a standalone client application for handling tasks such as system or application administration. For example, a web-based banking application might use an application client to manually administer customers and accounts. This capability is useful in the event the site becomes inaccessible for any reason or a customer prefers to communicate things like changes to account information by phone.

The Java 2 Enterprise Edition (J2EE) reference implementation provides a way for you to write, test, and deploy application clients. A J2EE application client is a standalone program launched from the command line or desktop, and typically accesses Enterprise JavaBean programs running on the J2EE application server.

This article shows you how to write, deploy, and test a simple application client. The application client is launched with the J2EE runclient command to administer customer information for a web-based banking application. The example has a Project Swing user interface.

About the Example

The application client for this article references enterprise bean and utility classes created by a third party and packaged into JAR files. The enterprise beans and classes in those JAR files handle customer, account, and transaction operations for a web-based banking application.

In this example, the application client references the CustomerControllerEJB bean in the customerBean JAR file. CustomerControllerEJB is a session bean that looks up and communicates with the CustomerEJB entity bean. The data storage details of the entity bean are hidden from the application client because the application client calls the session bean methods only. When the user adds or updates customer data, that data is first handled by the session bean where it is in a transient state, and passed to the entity bean where it is in a persistent state for a database read or write operation. If for any reason a database read or write operation cannot complete, the entire database transaction is backed out to prevent a condition where only part of a new record is recorded or only some of the updates for an existing record are committed. Because the session bean does not have direct access to the database, no such insurance is needed. Lost data that never made it to the entity bean can be reentered by the user, and data read from the database for display in the user interface can be retrieved in its original state.

To keep the complexity of the user interface code to a minimum, this application client implements only the three customer functions shown in Figure 1, which are view customer information, add a new customer to the database, and update customer information. These customer functions are implemented as methods available through the CustomerControllerBean session bean.

Banking Application

Figure 1: Example Application Client

Customer information is stored in the underlying Cloudscape database that comes with the J2EE installation. To read from and write to this database, the application client looks up and creates a reference to the CustomerControllerEJB session bean by way of its home and remote interfaces. Figure 2 shows how the application client and session bean work together once they are assembled into a complete J2EE application and deployed. The container, shown in the box within the circle is the interface between the session bean and the low-level platform-specific functionality that supports the session bean. The container is created during deployment.

client and session bean working together

Figure 2: Cooperating Classes and Interfaces

The application client does not work directly with the enterprise bean, but creates an instance of its home interface, CustomerControllerHome. The home interface extends EJBHome and has a create method for creating the enterprise bean in its container. CreateException is thrown if the enterprise bean cannot be created, and RemoteException is thrown if a communications-related exception occurs during the execution of a remote method.

When the home interface is created, the J2EE application server creates the remote interface, CustomerController, and enterprise bean, CustomerControllerEJB. The remote interface extends EJBObject and declares methods for creating and managing customer information. These methods are required to throw javax.rmi.RemoteException. The methods declared in the remote interface are implemented in the enterprise bean class.

The CustomerControllerEJB class provides implementations for the following methods, some of which are used in the example code for this article:

Write and Compile

A J2EE application client is written like any other Java programming language application. You write your class or classes using Java 2 Standard Edition (J2SE) and J2EE APIs, and compile as you normally would.

The J2EE application client for this article consists of the following classes: BankApp, EventHandle, and DataModel. You can read a detailed explanation of the code in Code Walkthrough below.

The application client classes are not in a package, so you can compile the BankApp class as shown below. The EventHandle and DataModel classes are compiled at the same time because BankApp references EventHandle and EventHandle references DataModel.

javac BankApp.java

Start the J2EE Server, Database, and Deploy Tool

To assemble, deploy, and test the J2EE application client, you start the J2EE server, database, and deploy tool as described here. First, download and install the J2SE and J2EE platforms. Once you have these platforms set up, you can start the J2EE server, database, and deploy tool.

In different windows, type the following commands in this order to start the J2EE server, Deploy tool, and Cloudscape database. Be sure the J2EE server is completely started before executing the deploytool and cloudscape commands:

  j2ee -verbose
  cloudscape -start

If that does not work, supply the fully-qualified pathname. For example, if your SDK installation is in a directory called JavaEE type this from the JavaEE directory:

Assemble the J2EE Application

Create a J2EE Application

The first step is to create a J2EE application to house the application client executables and enterprise bean JAR files.

File menu:

New Application dialog box:

Add Bean JARs to Application

Bean JAR files contain the third-party enterprise beans used by the application client.

File menu:

Add EJB JAR dialog box:

Create the Application Client

The application client must contain the application client executables and any classes those executables reference.

File menu:

Edit Contents of dialog box:

General dialog box:

Environment Entries dialog box:

Enterprise Bean References dialog box:

In this screen, you enter the home and remote interfaces for the CustomerControllerEJB session bean referenced by the application client. The coded name is the name defined in the com.sun.ebank.util.CodedNames class for the CustomerController remote interface.

Specify JNDI Names

With appclient selected, click the JNDI Names tab. Fill in the JNDI Name column as shown in Figure 4. The order may be a little different on your own display, but make sure you map the JNDI name you provide opposite the exact Component and Referenced By column as shown here. An explanation of these mappings immediately follows.

JNDI Names

Figure 4: Specifying JNDI Names

A JNDI name is the name the J2EE server uses to look up enterprise beans. In your code when you look up an enterprise bean, you supply statements similar to those shown below. The actual lookup takes place three lines down where the getCustomerControllerHome method is called on the EJBGetter class. The EJBGetter class is a utility class that retrieves a coded JNDI name from the src.com.sun.ebank.util.CodedNames class.

In this example, the application client is looking up the coded name for the CustomerController remote interface.

  try {
    customerControllerHome = 
    customer = customerControllerHome.create();
  } catch (Exception NamingException) {

If you look at the last line of the bottom table in the figure above, you see that BankApp (the display name for the main class for the application client) references ejb/customerController, which is the coded name defined in the CodedNames class for the CustomerController remote interface. Your job is to supply a JNDI name in the last column.

The JNDI name that you supply is stored in the J2EE application deployment descriptor and the J2EE server uses it to look up the CustomerControllerBean. If you look at the second row from the top in the top table in the figure above, you see that CustomerControllerBean is mapped to the same JNDI name as is ejb/customerController in the last line of the bottom table. It does not matter what JNDI name you supply, as long as you use the same name for the remote interface lookup as you use for its corresponding bean. So, looking at the table, you can say that the application client (BankApp) looks up the CustomerController remote interface, which uses the JNDI name of MyCustomerController, and the J2EE server uses the MyCustomerController JNDI name to find the corresponding CustomerControllerBean object.

The other rows in the top table have the mappings for the other enterprise beans. All of these beans are stored in the JAR files you added to the J2EE application during assembly. Their implementations have coded names for looking up either other enterprise beans or the database driver.

The JNDI name for the database driver is mapped in the bottom table and is jdbc/Cloudscape. This name is the default coded name supplied in the ~/javadkee/config file. You can use a different JNDI name for the database driver if you change the coded name in the ~/javadkee/config file.

Verify the J2EE Application

Before you deploy the J2EE application, it is a good idea to verify that the bean and application client code is compliant with the J2EE specification.

Tools menu:

Deploy the J2EE Application

Tools Menu:

Introduction dialog box:

JNDI Names dialog box:

Review dialog box:

Create the Database Tables

So the enterprise beans can write to and read from the database, you create the appropriate tables. To make things easy, the database tables are created with the following two scripts. These scripts create all the tables used by all the third-party enterprise beans; however, this example as it stands really only needs a database table for customer information. If you take this example and later expand it to use some of the other enterprise beans, the tables will be in the database ready for data.

Shift-click to download:

Put these files in the same directory anywhere on your system, make sure the Cloudscape database is running, and execute the cloudUtil script with create-table.sql parameter as follows:


Note: Your class path should point to J2sdkee/lib/system/tools.jar, and you might need to set J2EE_HOME to point to your javadkee installation.

Test the J2EE Application

To launch and test the example application client, set the APPCPATH environment variable to point to the directory where you stored the appclient.ear file and type the following at the command line:

  runclient -client appclient.ear -name BankApp en US

The -client appclient.ear parameter is the name of the J2EE application EAR file, and the -name BankApp parameter is the display name of the application client.

At run time, you'll need to set the APPCPATH environment variable to the client stub JAR, which contains the EJB class files. This is the path specified during deployment when you checked the box Return Client JAR. By default, the pathname for the returned jar file is the location where the EAR file is stored and the application client name with Client.jar appended as follows: ~/appclientClient.jar.

The en and US parameters passed to the runclient command are the language and country codes. The language and country codes in this example tell the application to use the English language (en) from the United States (US). See Internationalization below for more information on internationalizing and localizing an application.

When the login box appears, type in guest for the user name, and guest123 for the password, and click OK. The next thing you see is the application shown in Figure 5.

Banking Application

Figure 5: J2EE Application Client

Code Walkthrough

This section walks you through the code for the three classes that comprise the J2EE application client. Discussions on object-oriented program design and internationalization are included. You can, of course, skip these sections if you are already familiar with this material.

The Classes and their Relationship

The J2EE application client for this article is broken into the following three classes. Their relationship is depicted in Figure 6.

Flow of Control

Figure 6: Relationships among Classes

Object-Oriented Program Design

Organizing the application client code into classes according to function is a modular approach to application design that makes the application code easier to read, update, and maintain. For example, if you decide to remove the application client from the J2EE environment and run it as a stand alone application that accesses customer data in a file instead of a database, you only have to modify method implementations in the DataModel class. As long as you do not change the method signatures, you do not have to modify method signatures or implementations in other classes that call DataModel methods.

Another name for modularized programming like this is object-oriented programming. If you are new to object-oriented programming with the Java programming language, you might forget to design your application to use a separate class for each function and might not vigilant about making sure each class defines only one kind of function. You end up with one large class that combines functions and is essentially a procedural program wrapped up in one class. If you think you might be guilty of this, you are not alone. I have been guilty of it too.

There are a lot of texts that expound upon the concepts and benefits of object-oriented programming, but what I found to be the most enlightening was seeing a working program that does not use good object-oriented form and then seeing how to change it so it does. In that spirit, here is the BankAppNotOO code all in one class. The following discussions explain how to use a modular object-oriented approach to break this one very large class into three separate, organized, and smaller classes.

BankApp Class

The BankApp class creates the user interface, is the class with the main method and provides protected methods for the other BankApp application classes to call.

Main Method

The main method creates instances of the BankApp and EventHandle classes. The application is internationalized. The language and country variables passed to the BankApp constructor specify the language to use where en means English and US means United States. These values mean the application uses United States English as opposed to Australian or United Kingdom English.

The values for the language and country codes are retrieved from the args parameter passed to the main method. The args parameter gets its values when the application starts from values passed to the runclient command described in Test the J2EE Application above.

  public static void main(String args[]) {
    String language, country;
    if(args.length != 2) {
      language = new String("en");
      country = new String("US");
    } else {
      language = new String(args[0]);
      country = new String(args[1]);
    frame = new BankApp(language, country);
    frame.setTitle("Banking Administration Client");
    WindowListener l = new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
//Create event handling object
    EventHandle ehandle = new EventHandle(frame, 

The BankApp constructor creates the initial user interface, which consists of three buttons that let the user view customer information, add a new customer to the database, or update an existing customer's information. The internationalization code creates a Locale from the language and country parameters, and uses the Locale to create a ResourceBundle. The other parameter to the ResourceBundle is the first part of the name of the properties file where the translated text is stored. In this example, that file is MessagesBundle_en_US.properties, which you added to the application client during assembly.

  public BankApp(String language, String country) {
  //Internationalization variables
    Locale currentLocale;
    currentLocale = new Locale(language, country);
    messages = ResourceBundle.getBundle(
		"MessagesBundle", currentLocale);
  //Create initial UI (Panel 1)
    getContentPane().setLayout(new GridLayout(1,2));
    p1 = new JPanel();
    p1.setLayout(new GridLayout(11,1));
    p2 = new JPanel();
    view = new JButton(messages.getString(
    add = new JButton(messages.getString(
    update = new JButton(messages.getString(
    messlab = new JLabel();
    messlab2 = new JLabel();
    messlab3 = new JLabel();
    messlab4 = new JLabel();
    messlab5 = new JLabel();
    messlab6 = new JLabel();
    p1.add(new JLabel());
    //Create Panel 2 buttons so EventHandle 
    //constructor can add as action listeners
    OK = new JButton(
    cancel = new JButton(messages.getString(

//Add functionality to close window
    addWindowListener(new WindowAdapter() {
      public void windowClosing(
      WindowEvent event) {

In an internationalized program, string values are read from a properties file that contains translations for the language in use in the form of key and value pairs. So, instead of creating strings directly in your code, you create a ResourceBundle that indicates the file where the translations are and read the translations (values) from that file using the corresponding key. Here are the key and value pair definitions for the properties file used in this example:

  ViewButton=View Customer Information
  AddButton=Add New Customer
  UpdateButton=Update Customer Information

  ViewButtonMess=View Customer Information
  AddButtonMess=Add New Customer
  Update Customer Information
  EnterCustIDMess=Enter Customer ID:

  RemoteException=Remote Exception
  NotFoundException=not found
  State has two-letter limit
  MILimitException=MI has one-letter limit
  Missing required information

  FnameLab=First name (Required):
  LnameLab=Last name (Required):
  MiLab=MI (Required):
  StreetLab=Street (Required):
  CityLab=City (Required):
  StateLab=State (Required):

So, for example, instead of creating the view button like this:

  view = new JButton(
  "View Customer Information")

you would do it like this:

  view = new JButton(

In this example, ViewButton is the key in the MessagesBundle_en_US.properties file with a corresponding value of View Customer Information.

This approach makes it easy to localize application text to the language spoken by the majority of its users. For example, if you have another file named MessagesBundle_fr_FR.properties with French values for the keys, you could supply fr FR to the runclient command instead of en US to launch the application client to read in French from France instead of United States English.

Class Methods

The BankApp class provides methods that other objects call when they need to update the user interface. These methods are as follows:

The implementations are very straightforward, so rather than show it all here, you can browse the BankApp class file.

EventHandle Class

The EventHandle class implements the ActionListener interface, which provides a method interface for handling action events. Like all other interfaces in the Java programming language, ActionListener defines a set of methods, but does not implement their behavior. Instead, you provide the implementations because they take application-specific actions.

The ActionListener interface has only one method, the actionPerformed method. This method handles action events generated by the BankApp class when users interact with the user interface by clicking buttons. Figure 7 illustrates how the EventHandle class interacts with the BankApp and DataModel classes, and the following sections detail the illustration.

View an Event

Figure 7: Class Interactions


Events generated by the BankApp class are handled in the EventHandle class, so the EventHandle class not only implements the ActionListener interface with code to handle the events, but also listens for the button events generated by the BankApp class. To set up this connection, the EventHandle constructor receives an instance of the BankApp class, assigns it to its private instance variable, calls the addActionListener methods on the BankApp buttons, and passes the addActionListener methods an instance of itself (this) to add the EventHandle action listener to the buttons.

  public EventHandle(BankApp frame, 
		     ResourceBundle messages) {
    this.frame = frame;
    this.messages = messages;
    this.dataModel = dataModel;
    dataModel = new DataModel(frame, messages);

The constructor also receives an instance of the ResourceBundle class and assigns it to its private instance variable so the EventHandle object has access to the application client's localized text.

actionPerformed Method

The actionPerformed method handles the button events generated by the BankApp class. Its implementation uses a series of if statements to find out which button generated the event and takes the appropriate action. The bodies of the if statements change message text and the Panel 2 display in BankApp, and call methods in the DataModel class when data needs to be written to or read from the database, or when the Panel 2 display in BankApp needs to be updated in preparation for an add, view, or update operation.

  public void actionPerformed(
  ActionEvent event) {
    Object source = event.getSource();
//View customer data
    if(source == frame.view) {
     String vbutton = 
     messages.getString( "viewButton");
     frame.messlab5.setText(" " + vbutton);
     mess = new String(messages.getString(
     returned = JOptionPane.showInputDialog(frame, 
     if(returned != null) {
       which = 3;
       which, returned);
//Add new customer
   if(source == frame.add){
     String abutton = 
     frame.messlab5.setText(" " + abutton);
     which = 1;
     dataModel.createCustInf(which, returned);
//Update customer data
   if(source == frame.update){
     String ubutton = messages.getString(
     frame.messlab5.setText(" " + ubutton);
     mess = new String(messages.getString(
     returned = JOptionPane.showInputDialog(frame, 
     if(returned != null) {
       which = 2;
       dataModel.createCustInf(which, returned);
//Process data
   if(source == frame.OK) {
     if(which == 3) { //view data
     //add or update data
     } else if((which == 1) || (which == 2)) {
      //Test data and write to database
       int complete = dataModel.checkData(returned,
     //If data okay, clear Panel 2
       if(complete == 0) {
         if(which == 1) {
	     dataModel.custID, "Customer ID",
     //If errors, redisplay data to user
     //and leave error messages on display
       if(complete == 1) {
            dataModel.last, dataModel.mid, 
	    dataModel.str, dataModel.cty, 
	    dataModel.st, dataModel.zp, 
	    dataModel.tel, dataModel.mail);
//Clear data on cancel button press
   if(source == frame.cancel) {

DataModel Class

The DataModel class provides methods for reading data from the database, writing data to the database, retrieving data from the user interface, and checking that data before it is written to the database.

Figure 8 illustrates how the DataModel class interacts with the BankApp and EventHandle classes, and the following sections detail the illustration.

View a Model

Figure 8: Class Interactions


The constructor receives an instance of the BankApp class and assigns it to its private instance variable so the DataModel object can display error messages in the user interface when its checkData or writeData method detects errors. It also receives an instance of the ResourceBundle class and assigns it to its private instance variable so the DataModel object has access to the application client's localized text.

Because the DataModel class interacts with the database, the constructor also has the code to establish a connection with the remote interface for the CustomerController enterprise bean and use the remote interface to create an instance of the CustomerControllerEJB enterprise bean.

public DataModel(BankApp frame, 
		 ResourceBundle messages) {
  this.frame = frame;
  this.messages = messages;

//Look up and create CustomerController bean
  try {
    customerControllerHome = 
    customer = customerControllerHome.create();
  } catch (Exception NamingException) {

The getData method retrieves data from the user interface text fields and uses the String.trim method to remove extra control characters such as spaces and returns. Its one parameter is a JTextfield so any instance of the JTextfield class can be passed in for processing. This polymorphic approach saves the series of if statements used in the BankAppNotOO version.

private String getData(JTextField component) {
  String text, trimmed;
  if(component.getText().length() > 0) {
    text = component.getText();
    trimmed = text.trim();
    return trimmed;
  } else {
    text = null;
    return text;

The checkData method stores data retrieved by the getData method and checks the data to be sure all required fields have data, the middle initial is no longer than one character, and the state is no longer than two characters. If everything checks out, the writeData method is called. If there are errors, they are printed to the user interface in the BankApp object.

protected int checkData(
String returned, int which) {
  int i, j, k;
  this.which = which;

  last = getData(frame.lname);
  first = getData(frame.fname);
  mid = getData(frame.mi);
  str = getData(frame.street);
  cty = getData(frame.city);
  st = getData(frame.state);
  zp = getData(frame.zip);
  tel = getData(frame.phone);
  mail = getData(frame.e);


  //Check for data in required fields
  if((last != null) && (first != null) 
		&& (str != null) && (cty != null)
		&& (st != null)) {
    i = 0;
  } else {
    frame.messlab6.setText(" " + 
    i = 1;
  //Check middle initial length
  if(frame.mi.getText().length() > 1) {
    frame.messlab2.setText(" " + 
    j = 1;
   } else {
     j = 0;
  //Check state length
  if(frame.state.getText().length() > 2) {
    frame.messlab3.setText(" " + 
    k = 1;
  } else {
    k = 0;
  if((i == 0) && (j == 0) && (k == 0)) {
  //Write data to database
    int success = writeData();
    return success;
  } else {
    return 1;

The writeData method determines whether the operation is an add or an update and calls methods on the CustomerControlerEJB enterprise bean as appropriate. If the add or update operation fails, it writes error messages to the BankApp user interface.

private int writeData() {
  if(which == 2){ //Update customer information
    try {
      customer.setName(last, first, mid, returned);
      customer.setAddress(str, cty, st, zp, tel, 
			  mail, returned);
      return 0;
    } catch (RemoteException ex) {
      frame.messlab.setText(" " + 
      return 1;
    } catch (CustomerNotFoundException ex) {
      frame.messlab4.setText(" " + 
	    "CustomerException") +
            " " + returned + " " + 
      return 1;

  if(which == 1) { //Add new customer information
    try {
      custID = customer.createCustomer(
      last, first, mid, 
		str, cty, st, zp, tel, mail);
      return 0;
    } catch (RemoteException ex) {
      frame.messlab.setText(" " + 
      return 1;
  return 0;

The createCustInf method is called by the EventHandle class to refresh the Panel 2 display in the event of a view, update, or add action event.

protected void createCustInf(int which,
			     String returned) {
  CustomerDetails details = null;
  //View Data
  if((which == 3) && (returned.length() > 0)) {
    try {
      details = customer.getDetails(returned);
	), details.getCity(),
        details.getState(), details.getZip(),
    } catch (RemoteException ex) {
      frame.messlab.setText(" Remote Exception");
    } catch (CustomerNotFoundException ex) {
      frame.messlab4.setText(" " +
	messages.getString("CustomerException") +
	" " + returned + " " +
  //Update Data
  if((which == 2) && (returned.length() > 0)) {
    try {
      details = customer.getDetails(returned);
	details.getStreet(), details.getCity(),
	details.getState(), details.getZip(),
    } catch (RemoteException ex) {
      frame.messlab.setText(" Remote Exception");
    } catch (CustomerNotFoundException ex) {
      frame.messlab4.setText(" " +
	messages.getString("CustomerException") +
	" " +
        returned + " " + 
  //Add Data
  if(which == 1) {
    null, null, null, null, 
	null, null, null, null, null);


You might want to include an application client with your J2EE application for handling system or application administration. Deploying and running an application client is slightly different from deploying and running other J2EE components, but the code to connect to the enterprise beans running on the J2EE server is the same for all J2EE components.

The enterprise beans used for this article are part of a larger banking application, which is part of the J2EE Tutorial. The J2EE Tutorial is an excellent resource for the J2EE platform, tools, and APIs. For information on the banking application, please see Duke's Bank Application.

1 As used on this web site, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.

© 1994-2005 Sun Microsystems, Inc.