Testing HTML and JSF-Based UIs with Arquillian

How to use the testing framework to handle pure HTML, JSF, PrimeFaces, and Ajax in all major browsers

November 21, 2019

Download a PDF of this article

Arquillian is one of the principal frameworks for testing enterprise apps written in Java EE, Jakarta EE, or microservices. In a previous article, I explained how to configure Arquillian and use it for writing and executing tests dealing with enterprise application classes. In this article, I cover the use of Arquillian for testing front-end user interfaces. Specifically, I explain how to configure Arquillian for testing a Jakarta Server Faces front end and how to add the tests to the build process so those tests are executed in each build. (Note: JavaServer Faces was renamed Jakarta Server Faces when it was open sourced at the Eclipse Foundation.) After learning these concepts, you’ll be able to use Arquillian to develop tests for just about any UI framework. The entire project is available on GitHub, or you can download the listings for this article here.

Configuration for Front-End Testing

If you’re not familiar with Arquillian, you should read through the previous article, which explains the basics and how to get up and running.

There are a variety of technology stacks you can choose to test front ends with Arquillian. The correct choice depends on what type of testing is required. One option uses a combination of the Selenium WebDriver, Arquillian Drone, and Arquillian Graphene technologies. Another uses Arquillian Warp.

If the goal is to test front-end functionality in a reproducible manner using a straightforward programmatic approach, the first option is a great choice, because it allows tests to be created programmatically at a high level. I’ll refer to it in this article as Option 1. If the requirement is to test deeper, such as checking the value of requests during the different phases of the request lifecycle, the second option (referred to below as Option 2) is likely the best candidate.

Selenium WebDriver is a widely used technology for automating the actions of web browsers. It is especially useful for automating browsers to develop tests, but it can also be useful for performing administrative tasks and other automated processes. The Arquillian Drone extension can be used to integrate WebDriver with Arquillian, thereby providing benefits that go beyond using WebDriver alone. Those benefits include, among others, the ability to use multiple browsers for testing and simplifying configuration. Arquillian Drone also manages the lifecycle of tested browsers and makes tests easier to set up. The Arquillian Graphene extension enables you to develop tests targeted towards Ajax-based front ends, and it also provides tools to help you develop reusable tests. Arquillian Warp is an extension that allows you to write client-side tests that assert server-side logic.

Dependency configuration varies depending upon which technology stack you choose. In either case, only a couple of dependencies are required. If Arquillian has already been configured for the project, as described in my previous article, only a few additional dependencies are needed.

For the configuration of Option 1, dependencies from the Selenium WebDriver, Arquillian Drone, and Arquillian Graphene are required, as shown in Listing 1.

Listing 1. Maven dependencies for Option 1


<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.jboss.arquillian</groupId>
            <artifactId>arquillian-bom</artifactId>
            <version>1.1.11.Final</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency> <!-- Selenium bom is optional (see article) -->
            <groupId>org.jboss.arquillian.selenium</groupId>
            <artifactId>selenium-bom</artifactId>
            <version>2.53.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.extension</groupId>
            <artifactId>arquillian-drone-bom</artifactId>
            <version>2.0.1.Final</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

As part of the dependency chain for Option 1, Selenium can be included to bring in more-recent versions of the tool. However, if it is listed in the POM, it must be listed before arquillian-drone-bom.

Similarly, for Option 2, the Arquillian Warp dependency is required, as shown in Listing 2.

Listing 2. Maven dependencies for Option 2


<dependency>
  <groupId>org.jboss.arquillian.extension</groupId>
  <artifactId>arquillian-warp-jsf</artifactId>
  <version>1.0.0</version>
</dependency>

For the main example in this article, the technology stack I will use is WebDriver, Arquillian Drone, and Arquillian Graphene. Arquillian Warp contains many detailed options, allowing you to dig deep into the request lifecycle, create and test observers, and so on. This article does not cover Arquillian Warp, but I recommend you look into it.

Building a Test Class

The testing class setup for front-end tests is very similar to the setup for back-end testing. If you’ll recall, all Arquillian test classes are annotated with @RunWith(Arquillian.class). A ShrinkWrap archive containing the test and dependencies must be generated in a JAR or WAR format and deployed to an embedded or remote server to execute the test. The CLASSPATH is not used to create the archive; instead, a method annotated with @Deployment takes care of adding the resources required for executing the tests into the ShrinkWrap archive.

The main difference between front-end and back-end testing is that when the ShrinkWrap archive is created, it must generate a WebArchive (WAR) and all the web views that will be tested need to be added to the archive, along with all dependencies (including the template, CSS, JavaScript, and so on). To generate a complete application, resources such as WEB-INF and beans.xml must also be included, when required.

For the first example in this article, a testing class will be named CustomerCreationTest, and its purpose will be to test for the successful creation of a Customer entity via the user interface. The example project contains a series of Jakarta Server Faces views within a customer folder, which were generated by the Apache NetBeans JSF support and then modified to remove the use of a resource bundle and to provide custom functionality. A view named customer/Create.xhtml will be tested for proper functionality by ensuring that a Customer object can be created via the UI. Note that it is a good practice to include IDs for each component inside a web view so they can be referred to from the tests.

To set up the test deployment via ShrinkWrap, all the web resources that are part of the customer/Create.xhtml view must be included. Note that template.xhtml needs to also be part of the ShrinkWrap archive, because it is used by the view. The ShrinkWrap archive must be returned by a public static method that is annotated with @Deployment. A static final string should be declared for referencing the web pages folder with respect to the root of the project, as shown in the first line inside the class shown in Listing 3.

Listing 3. The deployment method and artifacts for the test class


@RunWith(Arquillian.class)
public class CustomerCreationTest {

    private static final String WEBAPP_SRC = "src/main/webapp";
    private static final String CUSTOMER_FOLDER = "/customer";

    @Deployment(testable = false)
    public static WebArchive createDeployment() {
        return ShrinkWrap.create(WebArchive.class, "CustomerCreation.war")
                .addClasses(Customer.class, 
                            CustomerFacade.class,
                            CustomerController.class, 
                            AbstractFacade.class, 
                            JsfUtil.class, 
                            PaginationHelper.class)
                .addAsWebResource(new File(WEBAPP_SRC, "template.xhtml"))
                .addAsWebResource(new File(WEBAPP_SRC, "index.xhtml"))
                .addAsWebResource(
                     new File(WEBAPP_SRC + "/resources/css", "cssLayout.css"))
                .addAsWebResource(
                     new File(WEBAPP_SRC + "/resources/css", "default.css"))
                .addAsWebResource(
                     new File(WEBAPP_SRC + "/resources/css", "jsfcrud.css"))
                .addAsWebResource(
                     new File(WEBAPP_SRC + "/resources/js", "jsfcrud.js"))
                .addAsWebResource(
                     new File(WEBAPP_SRC + CUSTOMER_FOLDER, "Create.xhtml"))
                .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
                .addAsResource("test-persistence.xml", "META-INF/persistence.xml")
                .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
                .addAsWebInfResource("mock-web.xml", "web.xml");
    }
. . .

As Listing 3 demonstrates, it can become tedious to add each of the web pages to the ShrinkWrap archive one by one. An ExplodedImporter and filter can be used to add all web pages at once. Listing 4 shows how to use an ExplodedImporter to pull all .xhtml pages into an archive.

Listing 4. Using an ExplodedImporter to pull all .xhtml pages into an archive


public static WebArchive createDeploymentBulk() {
        return ShrinkWrap.create(WebArchive.class, "create.war")
                .addClasses(Customer.class, 
                            CustomerFacade.class, 
                            CustomerController.class,
                            AbstractFacade.class, 
                            JsfUtil.class, 
                            PaginationHelper.class)
                .merge(ShrinkWrap.create(GenericArchive.class)
                                     .as(ExplodedImporter.class)
                                 .importDirectory(WEBAPP_SRC)
                                     .as(GenericArchive.class),
                        "/", 
                        Filters.include(".*\\.xhtml$"))
                .addAsWebInfResource(
                        EmptyAsset.INSTANCE, "beans.xml")
                .addAsWebInfResource(
                        new StringAsset(
                            "<faces-config version=\"2.3\"/>"),
                             "faces-config.xml");
 }

The approach shown in Listing 4 is also a good technique for developing reusable deployment methods.

Enabling Client Mode

One of the main differences between testing application front ends and testing back-end logic is that front-end tests need to be executed in client mode. Client mode enables the test to be executed inside the same JVM as the test runner. Rather than having Arquillian enrich the deployment archive and deploy the test to the container, the archive is executed as is outside the container when it is in client mode.

Client mode can be achieved a couple of ways. To enable the entire test class for running in test mode, mark the deployment method with testable=false, as follows:


@Deployment(testable = false)
    public static WebArchive
              createDeployment() {
    . . .
}

Marking the deployment as testable=false tells Arquillian not to repackage the @Deployment nor forward test execution to a remote server. If the @Deployment were marked as testable=true, that would indicate to Arquillian that the deployment needs to be repackaged, enriched, and tested in a remote container.

The second way to enable client mode is by marking individual methods of the class with @RunAsClient. By enabling client mode in this manner, some methods of the test class can be executed inside the container and others can be executed outside the container in client mode.

Creating Tests

Now that the deployment method has been created, it is time to build the tests. To gain control of the web browser, Selenium WebDriver must be injected into the class. This can be done via the use of the @Drone annotation:


@Drone
private WebDriver browser;

To open the browser to the correct page for testing, a reference to the root URI is required. To obtain this URI, the @ArquillianResource annotation can be used to inject the URI for the deployed test archive:


@ArquillianResource
private URL deploymentUrl;

The web elements that will be accessed by the test case are the Faces input components within the Jakarta Server Faces view that is being tested. Listing 5 shows the Create.xhtml view (abbreviated here for publication —Ed.), which has been modified from the IDE-generated view to include IDs for each web element.

Listing 5. The XHTML file to identify elements of the view to be tested


<h:form id="customerCreateForm" prependId="false">

    <h:messages id="message" />

    <h:panelGrid columns="2">
        <h:outputLabel value="Customer ID:" for="customerId" />
        <h:inputText id="customerId" 
                     value="#{customerController.selected.customerId}" 
                     title="Customer ID" required="true"
                     requiredMessage="Customer ID"/>
        <h:outputLabel value="Name:" for="name" />
        <h:inputText id="name" 
                     value="#{customerController.selected.name}" title="Name" />
        <h:outputLabel value="Address 1:" for="addressline1" />
        <h:inputText id="addressline1"
                     value="#{customerController.selected.addressline1}"
                     title="Address 1" />
...
    </h:panelGrid>
    <br />
    <h:commandButton id="createAction" 
                     action="#{customerController.create}" value="Create" />
    <br />
    <br />
    <h:commandButton id="showAllAction" 
                     action="#{customerController.prepareList}"
                     value="Show All" 
                     immediate="true"/>
    <br />
    <br />
    <h:link outcome="/index" value="Index"/>
</h:form>

To gain access to these elements via Selenium, use the @FindBy annotation, which provides the ability to find and obtain access to on-page elements by specifying an ID, a how expression, an xpath, or other optional elements.

In the CustomerCreationTest, a @FindBy is specified for each of the inputText components, the commandButton to create a user, and the messages component for displaying the onscreen message, as follows.


@FindBy(id = "message")
private WebElement facesMessage;

@FindBy(id = "customerId")
private WebElement customerId;

@FindBy(id = "name")
private WebElement name;

@FindBy(id = "addressline1")
private WebElement addressLine1;

. . .

@FindBy(id = "createAction")
private WebElement createAction;

Sometimes it may be beneficial to “view source” in the browser for the view being tested to determine the correct IDs, CSS selectors, and so on. Jakarta Server Faces can also append the form ID to the component ID in some cases.

The next step is to create the actual test, which is a Java method annotated with @Test that returns void. The test method can also be annotated with @RunAsClient if the test class is a client/server hybrid. As mentioned previously, this annotation denotes that the test should be run in client mode.

My test method is named create_customer_test(), and the code is shown in Listing 6.

Listing 6. The test to be run


@Test
@RunAsClient
public void create_customer_test() {
    browser.get(deploymentUrl.toExternalForm() + "faces/Create.xhtml");

    customerId.sendKeys("100");
    name.sendKeys("Joe Tester");
    addressLine1.sendKeys("123 Duke Street");
    addressLine2.sendKeys("");
    city.sendKeys("San Frantonio");
    state.sendKeys("IL");
    zip.sendKeys("95035");
    phone.sendKeys("555-1212");
    fax.sendKeys("?");
    email.sendKeys("tester@joe.com");
    creditLimit.sendKeys("10000");
    poolId.sendKeys("1");
    discountCode.sendKeys("N");

    guardHttp(createAction).click();

    Assert.assertNotNull(facesMessage);
    if(facesMessage != null){
    Assert.assertTrue("Customer Successfully Created",
        facesMessage.getText().contains("Successfully"));
    }
}

The first line of Listing 6 uses the injected WebDriver element to obtain a handle on the web page that will be controlled via the test. The next several lines of the method call upon WebElement sendKeys() to populate each of the onscreen input elements with text programmatically. Once populated, the WebElement that corresponds to the commandButton is clicked()to invoke the action that is bound to the CustomerController. If the action is successful, “Customer Successfully Created” will be populated into the message element with the ID of message.

The final lines of the test method check to ensure that the messages element is populated and then check to see if the text matches a successful creation.

The guardHttp function is known as a request guard. The Request Guard API is part of Graphene and it should be used to improve the reliability of your tests by allowing requests to be intercepted to verify that requests which occur after a method invocation are expected. Machines may vary with respect to resources, and resource guards should be put into place to alleviate variations between different machines by intercepting the request to ensure there is no interference when the request is executed. More information on request guards can be found in the documentation.

There are also the guardAjax and guardNoRequest guards, and a Waiting API. The Waiting API can ensure timing occurs properly in cases where a page has been updated in the background, JavaScript updates the page after a request is sent, or there is a redirect.

Executing the Test

There are several ways to execute Arquillian tests. The options are as follows: include them in the build process; use integrated tests with an IDE; or execute them from the command line. Refer to my previous article for more details.

When the UI tests are executed, a browser window will open briefly, execute the tests, and then close. If a test fails, it might be due to an issue with the ShrinkWrap, and it might not be easily detectable from the output in the server log until the error is traced up the stack. (As a reminder, ShrinkWrap is the standard packaging system used to assemble JARs, WARs, and EARs to be deployed directly by Arquillian.) Most ShrinkWrap errors occur due to a missing resource. The debugging process for Arquillian tests consists of looking through the output from the test execution as it is written to the command prompt or IDE during build or test runs, as well as parsing the output of the application server container if it is executing within a remote container. Some IDEs also have debugging available for tests, so breakpoints can be set within the tests to better assist in pinpointing problematic code. For example, in the following output, it is easy to see that the test failed due to invalid credentials for the remote server.


SEVERE: exit_code: FAILURE, message: Invalid user name or password. 
  [status: CLIENT_ERROR reason: Unauthorized]
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, 
  Time elapsed: 11.859 sec <<< FAILURE! - in 
org.javamagazine.arquillianexample.ui.CustomerUITest
org.javamagazine.arquillianexample.ui.CustomerUITest  
  Time elapsed: 11.859 sec  <<< ERROR!
com.sun.jersey.api.container.ContainerException: 
  exit_code: FAILURE, message: Invalid user name or password.
 [status: CLIENT_ERROR reason: Unauthorized]

If you are actively building a project and running tests, you may need to delete the target directory of the project if any of the source files change and rebuild the project. This is because Arquillian may reference these target source files, which results in import errors for the classes that have modified sources. Another thing to note is that after executing the tests in this example, the database records that were generated by the test need to be manually deleted, or the record removal can be coded as part of the tests.

Creating Reusable Tests

There are various ways to create reusable tests and reduce the number of lines that need to be coded or duplicated within each test. It is important to try to manage the size of tests so they do not grow out of control. It’s advantageous to write smaller test classes that can be put together to create complete tests or used within other tests.

The deployment method can be reused across various tests by extracting it from the test classes and placing it into a utility class. By doing so, the same deployment method can be used from multiple test classes—and this is especially the case if the deployment method has been made generic via the use of an ExplodedImporter.

When you create a deployment method for use across several test classes, it’s best to pull in classes at a package level using the ShrinkWrap addPackage() builder, as shown in Listing 7.

Listing 7. Creating a deployment method for use across several test classes

 


// Example of a universal deployment class for a project
public class Deployments {
    public static final String WEBAPP_SRC = "src/main/webapp";

    public static WebArchive getTestDeployment() {
        return ShrinkWrap.create(
            WebArchive.class, 
            "ArquillianExampleUI-Test.war")
                .addPackage("org.javamagazine.arquillianexample")
                .addPackage("org.javamagazine.arquillianexample.cdi")
                .addPackage("org.javamagazine.arquillianexample.cdi.util")
                .addPackage("org.javamagazine.arquillianexample.entity")
                .addPackage("org.javamagazine.arquillianexample.session")
                .merge(ShrinkWrap.create(GenericArchive.class)
                .as(ExplodedImporter.class)
                .importDirectory(WEBAPP_SRC).as(GenericArchive.class),
                     "/", Filters.include(".*\\.xhtml$"))
                .merge(ShrinkWrap.create(GenericArchive.class)
                .as(ExplodedImporter.class)
                .importDirectory(WEBAPP_SRC + "/customer").as(GenericArchive.class),
                     "/", Filters.include(".*\\.xhtml$"))
                .addAsManifestResource(EmptyAsset.INSTANCE,"beans.xml")
                .addAsResource("test-persistence.xml", "META-INF/persistence.xml")
                .addAsWebInfResource("mock-web.xml", "web.xml");
    }
}

If you use this technique, the deployment method should be made static so it can easily be used in the test classes.

Graphene

Graphene was addressed briefly when request guards were discussed earlier. However, for the most part, it is unobtrusive. That’s because Graphene works hand in hand with Arquillian to “automatically” enrich classes by wrapping the injected browser and enhancing the WebDriver API. There is no specific API that needs to be used to reap the benefits of Graphene; it is automatically applied. However, there are some bonus features that come with Graphene, which can be beneficial for eliminating duplication.

Graphene allows you to generate Page Objects, which provide a means to abstract web page logic from test classes so they can be reused in multiple tests. To create a Page Object, abstract into a separate class all portions of a test that pertain to a single page. As an example, Listing 8 shows a CreateCustomer.java (somewhat truncated for space —Ed.) that contains the WebElement declarations and test methods for the customer\Create.xhtml view.

Listing 8. Creating a Page Object (truncated listing)


package org.javamagazine.arquillianexample.ui;

import static org.jboss.arquillian.graphene.Graphene.guardHttp;
import static org.jboss.arquillian.graphene.Graphene.waitAjax;
import org.jboss.arquillian.graphene.page.Location;
import org.junit.Assert;
import static org.junit.Assert.assertEquals;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

/**
 * Abstraction of customer/Create.xhtml element.
 */
@Location("customer/Create.xhtml")
public class CreatePage {

    // Define the page logic
    @FindBy(id = "message")
    private WebElement facesMessage;

    @FindBy(id = "customerId")
    private WebElement customerId;

    @FindBy(id = "name")
    private WebElement name;

    @FindBy(id = "addressline1")
    private WebElement addressLine1;

    ...

    @FindBy(id = "createAction")
    private WebElement createAction;

    public void create_customer_test() {
        customerId.sendKeys("101");
        name.sendKeys("Joe Tester");
        addressLine1.sendKeys("123 Duke Street");
        addressLine2.sendKeys("");
        city.sendKeys("San Frantonio");
        state.sendKeys("IL");
        zip.sendKeys("95035");
        phone.sendKeys("555-1212");
        fax.sendKeys("?");
        email.sendKeys("tester@joe.com");
        creditLimit.sendKeys("10000");
        poolId.sendKeys("1");
        discountCode.sendKeys("N");

        guardHttp(createAction).click();

        Assert.assertNotNull(facesMessage);
        if (facesMessage != null) {
            Assert.assertTrue(
                "Customer Successfully Created",
                facesMessage.getText().contains("Successfully"));
        }
    }
}

The Page Object in Listing 8 can be injected into a standard test class and the tests can be executed from that class. The result is a leaner test class that contains less code and is easier to manage. Now that the page code is separated from the test class, it can also be brought into other test classes and used as needed.

The CreateCustomer class contains a @Location annotation, which wires the location information for the page to which the class belongs. Page Objects can be defined as nested classes, but separating them into their own class enables easier maintenance. To reference the Page Object from within the test class, set up a field for the class and annotate it with @Page. Listing 9 shows the complete sources for the test class CustomerUITest, which contains two Page Objects: CreatePage and EditPage.

Listing 9. A complete test class with two Page Objects


package org.javamagazine.arquillianexample.ui;

import java.math.BigDecimal;
import java.net.URL;
import org.javamagazine.arquillianexample.Deployments;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.junit.InSequence;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;

@RunWith(Arquillian.class)
public class CustomerUITest {
    
    @Deployment(order=1)
    public static WebArchive createDeployment() {
        return Deployments.getTestDeployment();
    }
    
    @Drone
    private WebDriver browser;
    
    @ArquillianResource
    private URL deploymentUrl;
    
    @Page
    CreatePage createPage;
    
    @Page
    EditPage editPage;
     
    @Test
    @InSequence(1)
    @RunAsClient
    public void create_customer_test(){
        browser.get(deploymentUrl.toExternalForm() + 
                    "faces/Create.xhtml");
        createPage.create_customer_test();        
    }   
    
    @Test
    @RunAsClient
    public void edit_customer_test(){
        browser.get(deploymentUrl.toExternalForm() + 
                    "faces/Edit.xhtml");
        editPage.edit_customer_test();
    }   
}

To break the tests down into even smaller and more manageable sources, there is the concept of Page Fragments. Page Fragments are similar to Page Objects, but instead of decoupling an entire HTML page structure, they abstract only a portion of a page.

A page fragment can be useful for abstracting commonly used component tests, such as an autocompletion component, so the fragments can be used within different Page Objects or test classes. To reference a Page Fragment, simply use the @FindBy annotation to inject it into the class to which it belongs.

Further UI Testing Concepts

Writing tests is just like writing regular code in that there are often many ways to achieve the same goal. The features explained in this article show just some of the ways that are useful for testing a user interface. As mentioned previously, Arquillian Warp is another API that can be useful for testing user interfaces, RESTful web services, and more. If you are using the PrimeFaces framework, the primefaces-arquillian project makes it easy to reference PrimeFaces components using the @FindBy annotation for more-complete testing.

Conclusion

Unit testing and functional testing are a phase of application development that cannot be overlooked. Moreover, the ability to incorporate both business logic and user interface tests into the build process helps to ensure that projects are more reliable. Arquillian contains many options for developing user interface tests. Arquillian, Drone, and Graphene options all make development of user interface tests rock solid.

Also in This Issue

Understanding the JDK’s New Superfast Garbage Collectors
Epsilon: The JDK’s Do-Nothing Garbage Collector
Understanding Garbage Collectors
Take Notes As You Code—Lots of ’em!
For the Fun of It: Writing Your Own Text Editor, Part 2
Quiz Yourself: Identify the Scope of Variables (Intermediate)
Quiz Yourself: Inner, Nested, and Anonymous Classes (Advanced)
Quiz Yourself: String Manipulation (Intermediate)
Quiz Yourself: Variable Declaration (Intermediate)
Book Review: The Pragmatic Programmer, 20th Anniversary Edition

Josh Juneau

Josh Juneau (@javajuneau) works as an application developer, system analyst, and database administrator. He primarily develops using Java and other JVM languages. He is a frequent contributor to Oracle Technology Network and Java Magazine and has written several books for Apress about Java and Java EE. Juneau was a JCP Expert Group member for JSR 372 and JSR 378. He is a member of the NetBeans Dream Team, a Java Champion, leader for the CJUG OSS Initiative, and a regular voice on the JavaPubHouse Off Heap podcast.

Share this Page