Saturday, February 13, 2010

JAX-RS and the REST Uniform Interface

In Roy Fielding's dissertation that started it all, he stated:

"The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components."


The above quotation can be found in Section 5.1.5 ("Uniform Interface") of Chapter 5 ("Representational State Transfer (REST)") of Fielding's now-famous PhD dissertation "Architectural Styles and the Design of Network-based Software Architectures."

The "uniform interface" concept is obviously important to REST and is a driving force behind the design of HTTP methods. JAX-RS provides sophisticated annotations-based support for providing uniform interfaces to clients from regular Java classes. The main intent of this post is to briefly cover how JAX-RS makes it easy to write Java classes that provide this uniform interface.

Nearly all REST-based applications "happen" to be HTTP based as well, but it is often emphasized that HTTP is not absolutely required to implement a REST-based application. Still, HTTP and REST often do go together, so it is not surprising that the JAX-RS-provided annotations @GET, @DELETE, @POST, and @PUT closely mirror in name the respective HTTP methods GET, DELETE, POST, and PUT. Note that JAX-RS also provides @OPTIONS and @HEAD annotations.

In my blog post JAX-RS with Jersey: An Introduction, I demonstrated use of the @GET annotation in conjunction with fellow JAX-RS annotations @Path and @PathParam. Only data retrieval (hence the "GET") was shown in that post. In this post, I expand upon (and change) the MovieOfTheDay class I used in that post to support creation/insertion of new data, updating of existing data, and deletion/removal of data.

As I stated in the previous post, I use an (slightly modified for this post) internal static map to emulate what would normally be a datastore such as a database. This piece of code is shown next.

MovieOfTheDay.java Fragment: Emulated Data Storage Portion

/** Overall simulated database of movies of the day. */
private static final Map<Integer, Map<Integer, String>> MOVIE_OF_THE_DAY;

static
{
MOVIE_OF_THE_DAY = new ConcurrentHashMap<Integer, Map<Integer, String>>();

final Map<Integer, String> janMovies = new ConcurrentHashMap<Integer, String>();
janMovies.put(10, "Trading Places");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JANUARY), janMovies);

final Map<Integer, String> febMovies = new ConcurrentHashMap<Integer, String>();
febMovies.put(2, "Groundhog Day");
febMovies.put(13, "Casablanca");
febMovies.put(14, "Sleepless in Seattle");
febMovies.put(15, "How to Lose a Guy in 10 Days");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.FEBRUARY), febMovies);

final Map<Integer, String> marMovies = new ConcurrentHashMap<Integer, String>();
marMovies.put(16, "The Fugitive");
marMovies.put(17, "Darby O'Gill and the Little People");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.MARCH), marMovies);

final Map<Integer, String> aprMovies = new ConcurrentHashMap<Integer, String>();
aprMovies.put(25, "Raiders of the Lost Ark");
aprMovies.put(26, "Indiana Jones and the Temple of Doom");
aprMovies.put(27, "Indiana Jones and the Last Crusade");
aprMovies.put(28, "Indiana Jones and the Kingdom of the Crystal Skull");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.APRIL), aprMovies);

final Map<Integer, String> mayMovies = new ConcurrentHashMap<Integer, String>();
mayMovies.put(26, "Star Wars: Episode 1 - The Phantom Menace");
mayMovies.put(27, "Star Wars: Episode 2 - Attack of the Clones");
mayMovies.put(28, "Star Wars: Episode 3 - Revenge of the Sith");
mayMovies.put(29, "Star Wars: Episode 4 - A New Hope");
mayMovies.put(30, "Star Wars: Episode 5 - The Empire Strikes Back");
mayMovies.put(31, "Star Wars: Episode 6 - Return of the Jedi");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.MAY), mayMovies);

final Map<Integer, String> junMovies = new ConcurrentHashMap<Integer, String>();
junMovies.put(15, "The Great Outdoors");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JUNE), junMovies);

final Map<Integer, String> julMovies = new ConcurrentHashMap<Integer, String>();
julMovies.put(4, "Independence Day");
julMovies.put(21, "Men in Black");
julMovies.put(22, "Men in Black 2");
julMovies.put(23, "I, Robot");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JULY), julMovies);

final Map<Integer, String> augMovies = new ConcurrentHashMap<Integer, String>();
augMovies.put(10, "North by Northwest");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.AUGUST), augMovies);

final Map<Integer, String> sepMovies = new ConcurrentHashMap<Integer, String>();
sepMovies.put(1, "Dead Poets Society");
sepMovies.put(20, "Uncle Buck");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.SEPTEMBER), sepMovies);

final Map<Integer, String> octMovies = new ConcurrentHashMap<Integer, String>();
octMovies.put(28, "Psycho");
octMovies.put(29, "Sixth Sense");
octMovies.put(30, "Ghostbusters");
octMovies.put(31, "Young Frankenstein");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.OCTOBER), octMovies);

final Map<Integer, String> novMovies = new ConcurrentHashMap<Integer, String>();
novMovies.put(10, "The Italian Job");
novMovies.put(11, "Ocean's Eleven");
novMovies.put(12, "Ocean's Twelve");
novMovies.put(13, "Ocean's Thirteen");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.NOVEMBER), novMovies);

final Map<Integer, String> decMovies = new ConcurrentHashMap<Integer, String>();
decMovies.put(1, "Holiday Inn");
decMovies.put(24, "It's A Wonderful Life");
decMovies.put(25, "A Christmas Carol");
decMovies.put(26, "A Christmas Story");
decMovies.put(27, "Home Alone");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.DECEMBER), decMovies);
}


The above code snippet sets up an underlying data store with the movies of the day. The MovieOfTheDay class represents a resource and its class declaration is shown in the next code listing. The @Path annotation is significant because it tells the JAX-RS provider that this class represents a resource.

MovieOfTheDay.java Fragment: Class Declaration with @Path

/**
* Simple class that provides a movie for the provided month and day of that
* month.
*/
@Path("/movies")
public class MovieOfTheDay
{
// . . .
}


By specifying "/movies" as an argument to the @Path annotation, I am instructing the JAX-RS provider that this class represents a resource accessible in part by the URI portion "/movies".

With the @Path annotation provided on the class level, methods can be defined for the JAX-RS provider to tie to the uniform interface. The next code snippet demonstrates this for GET. There are actually two methods annotated with the @GET annotation. The first method, getMovieUsage(), simply returns a String specifying how the other GET method can be used. The other GET method is called getMovieOfTheDay(Integer,Integer) and it provides the movie of the day for the day provided as the two integer parameters (month and date in that month).

MovieOfTheDay.java Fragment: Two GET Methods

/**
* This method provides a "usage" string related to retrieving movie of the
* day information.
*
* @return String describing how to access movie of the day information.
*/
@GET
@Path("/")
@Produces("text/plain")
public String getMovieUsage()
{
return
"To see the movie of the day, provide URL with month and day: "
+ "\thttp://localhost:8080/rest/resources/movies/<<month>>/<<day>>";
}

/**
* Obtain the movie of the day as indicated by the provided month and date.
*
* @param month Month for which movie of the day is desired.
* @param date Date for which movie of the day is desired.
* @return Title of the movie of the day for provided month and date.
*/
@GET
@Path("/{month}/{date}")
@Consumes("text/plain")
@Produces("text/html")
public String getMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
final String movieOfTheDay = moviesOfTheMonth != null
? moviesOfTheMonth.get(date)
: "Fletch Lives!";
return movieOfTheDay != null
? generateSelectedMovieHtml(movieOfTheDay, month, date)
: generateSelectedMovieHtml("Fletch", month, date);
}


The @Path annotation was used on this entire class and then was again used in the previous code sample to annotate the two GET methods to add more to the overall path. In the first case, the usage method's path is defined simply as "/", meaning that it remains the same path as to the overall class. The second GET method, however, has an @Path annotation with "/month/date" specified as an argument. This means that the JAX-RS provider should match this method with a URI containing the class-level @Path's URI portion concatenated with this method's @Path's URI portion: movies/{month}/{date}. The curly braces indicate that the value is a placeholder. In this case, the {month} and {date} are placeholders for Integers (the types of the two parameters to the method that are annotated with @PathParam annotations that tie them directly to the placeholder names).

The code for the other methods on this HTTP-based uniform interface is similar @DELETE annotation makes it obvious which method is associated with deletion of a resource. In my case, I'm using the method associated with the @PUT annotation for both insert/create (new) behavior and for update/modify (change to existing) behavior. There has been controversy in the REST community about how closely (if at all) HTTP methods in HTTP-based REST applications should apply to the CRUD concept (Create/Read/Update/Delete) with the particularly contentious point being the relationship of HTTP methods PUT/POST to the CRUD operations of UPDATE/CREATE.

My current favorite treatment of the POST/PUT issue is John Calcote's blog post PUT or POST: The REST of the Story. For me, whether to use POST or PUT does come down to asking myself the question, "Is the modeled behavior idempotent?" If I answer "yes" (repeated calls always end up with same value/state), then I use PUT. If the modeled behavior is not idempotent (the same call leads to different results), then I use POST. As a side note, it is interesting that "Idempotence" was Eric Lippert's first covered word in his series Five-Dollar Words for Programmers.

Returning to the subject of JAX-RS and the uniform interface, here is a code snippet with the appropriate JAX-RS annotations for deleting and inserting/updating movies of the day.

MovieOfTheDay.java Fragment: DELETE and PUT Methods

/**
* Remove the movie of the day for the day whose month and date are provided
* as parameters.
*
* @param month Month of date for which movie is to be removed.
* @param date Date for which movie is to be removed.
* @return String representing movie of the day information was deleted.
*/
@DELETE
@Path("/{month}/{date}")
@Consumes("text/plain")
@Produces("text/html")
public String deleteMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
String movieRemoved = "";
if (moviesOfTheMonth != null)
{
movieRemoved = moviesOfTheMonth.remove(date);
}
return generateDeletedMovieHtml(movieRemoved);
}

/**
* Add a new entry or update an existing entry representing a movie of the day.
*
* @param month Month for the movie of the day.
* @param date Day of the month for the movie of the day.
* @param title Title of the movie of the day.
* @return HTML excerpt for movie of the day entry status.
*/
@PUT
@Path("/{month}/{date}/{title}")
@Consumes("text/plain")
@Produces("text/html")
public String addMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date,
@PathParam("title") final String title)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
if (moviesOfTheMonth != null)
{
moviesOfTheMonth.put(date, title);
}
return generateAddedMovieHtml(title);
}


The code shown to this point in this post illustrates use of JAX-RS annotations @GET, @DELETE, and @PUT to expose methods in the Java class as methods roughly equivalent respectively to reading/retrieving, deleting, and adding/updating. Before moving onto screen snapshots showing these simple examples in action, I provide the entire class (including the example snippets above and the helper utility methods called from the examples above) here in one listing for convenience.

MovieOfTheDay.java: The Complete Class

package rmoug.td2010.rest;

import java.util.Calendar;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;

/**
* Simple class that provides a movie for the provided month and day of that
* month.
*/
@Path("/movies")
public class MovieOfTheDay
{
/** Handle to logger. */
private static final Logger LOGGER = Logger.getLogger("rmoug.td2010.rest.MovieOfTheDay");

/** Overall simulated database of movies of the day. */
private static final Map<Integer, Map<Integer, String>> MOVIE_OF_THE_DAY;

static
{
MOVIE_OF_THE_DAY = new ConcurrentHashMap<Integer, Map<Integer, String>>();

final Map<Integer, String> janMovies = new ConcurrentHashMap<Integer, String>();
janMovies.put(10, "Trading Places");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JANUARY), janMovies);

final Map<Integer, String> febMovies = new ConcurrentHashMap<Integer, String>();
febMovies.put(2, "Groundhog Day");
febMovies.put(13, "Casablanca");
febMovies.put(14, "Sleepless in Seattle");
febMovies.put(15, "How to Lose a Guy in 10 Days");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.FEBRUARY), febMovies);

final Map<Integer, String> marMovies = new ConcurrentHashMap<Integer, String>();
marMovies.put(16, "The Fugitive");
marMovies.put(17, "Darby O'Gill and the Little People");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.MARCH), marMovies);

final Map<Integer, String> aprMovies = new ConcurrentHashMap<Integer, String>();
aprMovies.put(25, "Raiders of the Lost Ark");
aprMovies.put(26, "Indiana Jones and the Temple of Doom");
aprMovies.put(27, "Indiana Jones and the Last Crusade");
aprMovies.put(28, "Indiana Jones and the Kingdom of the Crystal Skull");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.APRIL), aprMovies);

final Map<Integer, String> mayMovies = new ConcurrentHashMap<Integer, String>();
mayMovies.put(26, "Star Wars: Episode 1 - The Phantom Menace");
mayMovies.put(27, "Star Wars: Episode 2 - Attack of the Clones");
mayMovies.put(28, "Star Wars: Episode 3 - Revenge of the Sith");
mayMovies.put(29, "Star Wars: Episode 4 - A New Hope");
mayMovies.put(30, "Star Wars: Episode 5 - The Empire Strikes Back");
mayMovies.put(31, "Star Wars: Episode 6 - Return of the Jedi");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.MAY), mayMovies);

final Map<Integer, String> junMovies = new ConcurrentHashMap<Integer, String>();
junMovies.put(15, "The Great Outdoors");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JUNE), junMovies);

final Map<Integer, String> julMovies = new ConcurrentHashMap<Integer, String>();
julMovies.put(4, "Independence Day");
julMovies.put(21, "Men in Black");
julMovies.put(22, "Men in Black 2");
julMovies.put(23, "I, Robot");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JULY), julMovies);

final Map<Integer, String> augMovies = new ConcurrentHashMap<Integer, String>();
augMovies.put(10, "North by Northwest");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.AUGUST), augMovies);

final Map<Integer, String> sepMovies = new ConcurrentHashMap<Integer, String>();
sepMovies.put(1, "Dead Poets Society");
sepMovies.put(20, "Uncle Buck");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.SEPTEMBER), sepMovies);

final Map<Integer, String> octMovies = new ConcurrentHashMap<Integer, String>();
octMovies.put(28, "Psycho");
octMovies.put(29, "Sixth Sense");
octMovies.put(30, "Ghostbusters");
octMovies.put(31, "Young Frankenstein");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.OCTOBER), octMovies);

final Map<Integer, String> novMovies = new ConcurrentHashMap<Integer, String>();
novMovies.put(10, "The Italian Job");
novMovies.put(11, "Ocean's Eleven");
novMovies.put(12, "Ocean's Twelve");
novMovies.put(13, "Ocean's Thirteen");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.NOVEMBER), novMovies);

final Map<Integer, String> decMovies = new ConcurrentHashMap<Integer, String>();
decMovies.put(1, "Holiday Inn");
decMovies.put(24, "It's A Wonderful Life");
decMovies.put(25, "A Christmas Carol");
decMovies.put(26, "A Christmas Story");
decMovies.put(27, "Home Alone");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.DECEMBER), decMovies);
}

/**
* This method provides a "usage" string related to retrieving movie of the
* day information.
*
* @return String describing how to access movie of the day information.
*/
@GET
@Path("/")
@Produces("text/plain")
public String getMovieUsage()
{
return
"To see the movie of the day, provide URL with month and day: "
+ "\thttp://localhost:8080/rest/resources/movies/<<month>>/<<day>>";
}

/**
* Obtain the movie of the day as indicated by the provided month and date.
*
* @param month Month for which movie of the day is desired.
* @param date Date for which movie of the day is desired.
* @return Title of the movie of the day for provided month and date.
*/
@GET
@Path("/{month}/{date}")
@Consumes("text/plain")
@Produces("text/html")
public String getMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
final String movieOfTheDay = moviesOfTheMonth != null
? moviesOfTheMonth.get(date)
: "Fletch Lives!";
return movieOfTheDay != null
? generateSelectedMovieHtml(movieOfTheDay, month, date)
: generateSelectedMovieHtml("Fletch", month, date);
}

/**
* Remove the movie of the day for the day whose month and date are provided
* as parameters.
*
* @param month Month of date for which movie is to be removed.
* @param date Date for which movie is to be removed.
* @return String representing movie of the day information was deleted.
*/
@DELETE
@Path("/{month}/{date}")
@Consumes("text/plain")
@Produces("text/html")
public String deleteMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
String movieRemoved = "";
if (moviesOfTheMonth != null)
{
movieRemoved = moviesOfTheMonth.remove(date);
}
return generateDeletedMovieHtml(movieRemoved);
}

/**
* Add a new entry or update an existing entry representing a movie of the day.
*
* @param month Month for the movie of the day.
* @param date Day of the month for the movie of the day.
* @param title Title of the movie of the day.
* @return HTML excerpt for movie of the day entry status.
*/
@PUT
@Path("/{month}/{date}/{title}")
@Consumes("text/plain")
@Produces("text/html")
public String addMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date,
@PathParam("title") final String title)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
if (moviesOfTheMonth != null)
{
moviesOfTheMonth.put(date, title);
}
return generateAddedMovieHtml(title);
}

/**
* Generate snippet of HTML related to selection of a movie.
*
* @param movieTitle Title of movie selected.
* @param movieMonth Month in which this was movie of the day.
* @param movieDay Day on which this was movie of the day.
* @return HTML describing selected movie of the day.
*/
private String generateSelectedMovieHtml(
final String movieTitle, final int movieMonth, final int movieDay)
{
final String movieOfTheDayBody =
"<p>The movie of the day for " + movieMonth + "/" + movieDay
+ " is '" + movieTitle + "'.</p>";
return generateHtmlTemplate("Movie of the Day", movieOfTheDayBody);
}

/**
* Generate snippet of HTML related to deletion of a movie.
*
* @param deletedMovieTitle Title of movie that was deleted.
* @return HTML describing movie that was deleted.
*/
private String generateDeletedMovieHtml(
final String deletedMovieTitle)
{
final String deletedMovieBody =
deletedMovieTitle != null
? "<p>The movie '" + deletedMovieTitle + "' was removed.</p>"
: "<p>No movie was deleted because no matching movie found.</p>";
return generateHtmlTemplate("Deleted Movie", deletedMovieBody);
}

/**
* Generate snippet of HTML related to added/updated movie information.
*
* @param addedMovieTitle Title of movie being added/updated.
* @return HTML describing added/updated movie.
*/
private String generateAddedMovieHtml(final String addedMovieTitle)
{
final String addedMovieBody =
addedMovieTitle != null
? "<p>The movie '" + addedMovieTitle + "' was added as the Movie of the Day."
: "<p>No movie added for movie of the day.</p>";
return generateHtmlTemplate("Added Movie", addedMovieBody);
}

/**
* Generic HTML template generated from provided HTML title and body.
*
* @param htmlTitle Title portion of HTML document to be generated.
* @param htmlBody Body portion of HTML document to be generated.
* @return HTML template populated with provided head and body.
*/
private String generateHtmlTemplate(
final String htmlTitle, final String htmlBody)
{
final StringBuilder builder = new StringBuilder();
builder.append("<html>")
.append("<head><title>")
.append(htmlTitle)
.append("</title></head>")
.append("<body><span style='font-weight: bold; font-size: 125%;")
.append("text-align: center;'>")
.append(htmlTitle)
.append("</span>");
builder.append(htmlBody);
builder.append("</body></html>");
return builder.toString();
}
}


In my introductory post on JAX-RS with GlassFish, I showed the web.xml file I used to deploy the JAX-RS-annotated class properly on GlassFish. That file is shown here again for convenience.

web.xml

?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>ServletAdaptor</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>ServletAdaptor</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>

<session-config>
<session-timeout>
30
</session-timeout>
</session-config>

</web-app>


The previously referenced blog post provided relatively deep coverage of deploying the JAX-RS application to GlassFish v3, so I won't go into that again here. In that same post, I used a web browser as my simple REST client because I only used methods accessed via HTTP GET. In this post, however, I am also demonstrating DELETE and PUT, so I will use RESTClient. I have blogged previously on using RESTClient (in Easy Java-Based REST Testing Tools and in Using RESTClient with Java's HTTPServlet).

The first screen snapshot demonstrates running RESTClient with the method exposed via HTTP GET that provides usage information.



The next screen snapshot demonstrates the other GET-exposed method. This method provides an actual movie of the day.



In the previous two screen snapshots, the GET HTTP method was used as shown by the checked GET bullet. The next screen snapshot shows a movie of the day being added. In this case, the PUT radio bullet is checked on REST Client. The URL changes here to include a movie title.



I needed to specify the space in the movie title ("Mission Impossible") with %20 in this case. In a realistic, programmatic REST client, I'd make sure either the client framework or my own code ensured that spaces were translated for users rather than relying forcing the client to handle these %20 representations.

To call the deletion method, I simply need to change the HTTP method selected in the RESTClient GUI to "DELETE" and return to the URI with month and date integers. This is shown in the next screen snapshot.



If I run RESTClient GET with the same URI at this point, I'll get a message back saying that the movie of the day is Fletch because that is the default I use for any dates on which no movie of the day is assigned (and I had just deleted the movie assigned to that day).

JAX-RS supports REST in many more ways besides its making it easy to tie custom methods to specific HTTP methods, but I wanted to focus on how JAX-RS enables the uniform interface concept via the standard HTTP methods in this post. In future posts, I plan to consider how JAX-RS further helps with an HTTP-based REST application by handling other items such as content types and responses.

JAX-RS 1.1: What's New?

Srini Penchikala's InfoQ article Java EE 6 Web Services: JAX-RS 1.1 Provides Annotation Based REST Support provides a concise (fits on one printed page!) high-level overview of JAX-RS 1.1, some of the commonly used JAX-RS annotations, and some common implementations of JAX-RS 1.1. It's definitely worth the short read for anyone just starting to learn about JAX-RS. JAX-RS 1.x has been developed under the Java Community Process via JSR 311 ("JSR 311: JAX-RS: The Java API for RESTful Web Services").

The JAX-RS 1.1 changelog documents changes from JAX-RS 1.0 to 1.1. One of the most significant characteristics of JAX-RS 1.1 is that it is part of Java EE 6 and so multiple items related to inclusion with Java EE 6 are included. Several minor, but appreciated, enhancements surround adding examples to, clarifying, and correcting Javadoc comments related to use of JAX-RS.

One of the interesting JAX-RS 1.1 enhancements is changing the enum Response.Status to implement an interface called Response.StatusType. This is interesting from an intellectual or conceptual perspective that it illustrates the usefulness of having a Java enum implement an interface. In this case, this allows JAX-RS 1.1 to get around limitations associated with not being able to extend an enum. In particular (and this is where this is interesting from a practical perspective), this allows developers to express their own custom response status types by custom enum.

The new @ApplicationPath annotation can be used on a custom extension of Application to specify the base URI for all paths specified with the @Path annotation. This is similar to specifying this in the web.xml file and, in fact, the any setting in the web.xml file still takes precedence over the @ApplicationPath value. I generally prefer this strategy (used also in Java Persistence API) of having external XML/configuration be able to override/supersede in-code annotations.

It is not unusual to have a version 1.1 of a specification focus on improvements to the 1.0 version found during "real life" application of that specification. JAX-RS 1.1 is no exception and has several changes related to improving JAX-RS by making standard supported behavior less ambiguous. For example, JAX-RS 1.1 requires implementations to use application/octet-stream as the default Content-Type. JAX-RS 1.1 also requires compliant implementations to support a static fromString method in a type describing a resource model parameter.

There are several other goodies added to JAX-RS 1.1 and these can be seen in the previously mentioned changelog. Markus Karg's blog post "WebDAV Support for JAX-RS" Release 1.1 discusses how changes in JAX-RS 1.1 enabled him to simplify WebDAV support for JAX-RS.

The article that I referenced at the beginning of this post (Java EE 6 Web Services: JAX-RS 1.1 Provides Annotation Based REST Support) outlines the versions of several JAX-RS implementations that support JAX-RS 1.1. These implementations of JAX-RS 1.1 are Jersey 1.1.5, Apache CXF 2.3, and RESTEasy. Other JAX-RS implementations include Restlet and Apache Wink (incubator).

JAX-RS 1.1 introduces some nice changes to JAX-RS that one normally expects when going from a 1.0 to a 1.1. Not the least of these advantages is its inclusion in Java EE 6.

Wednesday, February 10, 2010

Java: "duplicate class" and Mismatched File Name Error

Developers who are new to Java can sometimes have trouble with class and package naming. In fact, the introductory Java forums are filled with threads starting with questions about these areas of Java. In this blog post, I look at some of these errors and some of the causes of these errors.

One of the more obvious errors occurs when a public Java class is named differently than the file that contains the class definition.

This is demonstrated in the next screen snapshot. In this example, a class was declared as public with the name Person, but was saved in a file called Person2.java. The error message is pretty explicit: "class Person is public, should be declared in a file named Person.java"



The "duplicate class" error can sometimes be a little more tricky to resolve. One situation in which this occurs is when two source code directories include the same class with the same package structure. This error looks like that shown in the next screen snapshot.



As the above screen snapshot indicates, the class dustin.examples.Person exists in both the src2 directory and (not shown here) in the src directory ("duplicate class: dustin.examples.Person"). Indeed, these are duplicate classes, at least in terms of package and class name.

The "duplicate class" error can also occur when the class is named the same with the same package naming hierarchy, even if one of the classes exists in a directory structure with directory names different than the package names. This is shown in the next screen snapshot.



This screen snapshot demonstrates that the "duplicate error" occurs when the class names and declared package names match even if the source file exists in directories with different names (different even than the declared package structure). What this implies is that if a particular class was copied to another directory without changing the package statement and the new directory was in the source path for javac, the "duplicate class" error will occur.

Another interesting observation from the last example is that javac uses the package statement to decide where to build the .class file rather than using the source file's location in a directory structure.

In the blog post Java duplicate class error, Morgy describes a situation in which he ran into the "duplicate class" error message (he had neglected to place the class's package declaration at the top of the class).

Conclusion

In this post, I have attempted to show some common causes of errors that can be troublesome for those new to Java. Specifically, I have demonstrated some of the common causes of the "duplicate class" error and of the public class not matching its file name.

Tuesday, February 9, 2010

JAX-RS with Jersey: An Introduction

The JAX-RS (JSR 311: The Java API for RESTful Web Services) specification provides a standardized Java-based approach to implementing REST-style web services. Jersey is the reference implementation of JAX-RS and I provide a brief introduction to JAX-RS via Jersey in this blog post.

Although Jersey doesn't require the use of GlassFish, I use Jersey in conjunction with GlassFish v3 in this post. GlassFish v3 provides the reference implementation for Java EE 6. I download the GlassFish v3 Windows Installer and ran it for installation. After its installation, I set the environment variable GLASSFISH_HOME to point to the installation's root directory and added %GLASSFISH_HOME% to my PATH. GlassFish can then be run with the command asadmin start-domain (starts the default domain) as demonstrated in the next screen snapshot.



Because I used the default settings during my installation of GlassFish, the web-based administrative console is available on my machine at the URI http://localhost:4848/ (default port is 4848). With GlassFish running, this URI leads to the login page for the Administrative Console. The administrative username and password were specified in the installation. This screen is shown in the next screen snapshot.




With GlassFish set up, I now move onto developing a very simple REST application using Jersey. I start with a properly JAX-RS-annotated class called MovieOfTheDay:

MovieOfTheDay.java

package rmoug.td2010.rest;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.ws.rs.GET;

import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;

/**
* Simple class that provides a movie for the provided month and day of that
* month.
*/
@Path("/movies")
public class MovieOfTheDay
{
private static final Logger LOGGER = Logger.getLogger("rmoug.td2010.rest.MovieOfTheDay");

private static final Map<Integer, Map<Integer, String>> MOVIE_OF_THE_DAY;

static
{
MOVIE_OF_THE_DAY = new HashMap<Integer, Map<Integer, String>>();

final Map<Integer, String> janMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JANUARY), janMovies);

final Map<Integer, String> febMovies = new HashMap<Integer, String>();
febMovies.put(2, "Groundhog Day");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.FEBRUARY), febMovies);

final Map<Integer, String> marMovies = new HashMap<Integer, String>();
marMovies.put(16, "The Fugitive");
marMovies.put(17, "Darby O'Gill and the Little People");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.MARCH), marMovies);

final Map<Integer, String> aprMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.APRIL), aprMovies);

final Map<Integer, String> mayMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.MAY), mayMovies);

final Map<Integer, String> junMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JUNE), junMovies);

final Map<Integer, String> julMovies = new HashMap<Integer, String>();
julMovies.put(4, "Independence Day");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JULY), julMovies);

final Map<Integer, String> augMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.AUGUST), augMovies);

final Map<Integer, String> sepMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.SEPTEMBER), sepMovies);

final Map<Integer, String> octMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.OCTOBER), octMovies);

final Map<Integer, String> novMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.NOVEMBER), novMovies);

final Map<Integer, String> decMovies = new HashMap<Integer, String>();
decMovies.put(24, "It's A Wonderful Life");
decMovies.put(25, "A Christmas Carol");
decMovies.put(26, "A Christmas Story");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.DECEMBER), decMovies);
}

@GET
@Path("/")
@Produces("text/plain")
public String getMovie()
{
return
"To see the movie of the day, provide URL with month and day: "
+ "\thttp://localhost:8080/rest/resources/movies/<<month>>/<<day>>";
}

/**
* Obtain the movie of the day as indicated by the provided month and date.
*
* @param month Month for which movie of the day is desired.
* @param date Date for which movie of the day is desired.
* @return Title of the movie of the day for provided month and date.
*/
@GET
@Path("/{month}/{date}")
@Consumes("text/plain")
@Produces("text/html")
public String getMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
final String movieOfTheDay = moviesOfTheMonth != null
? moviesOfTheMonth.get(date)
: "Fletch";
return movieOfTheDay != null
? generateHtml(movieOfTheDay, month, date)
: generateHtml("Fletch Lives!", month, date);
}

private String generateHtml(
final String movieTitle, final int movieMonth, final int movieDay)
{
final StringBuilder builder = new StringBuilder();
builder.append("<html>")
.append("<head><title>Movie of the Day</title></head>")
.append("<body><span style='font-weight: bold; font-size: 125%;")
.append("text-align: center;'>Movie of the Day</span>")
.append("<p>The movie of the day for ")
.append(movieMonth)
.append("/")
.append(movieDay)
.append(" is '")
.append(movieTitle)
.append("'.</p></body></html>");
return builder.toString();
}
}


The static initialization block is not specific to JAX-RS, but is instead used to simulate a database. In a real REST application, I'd almost certainly have a database on the back-end, but the in-memory static map simulates that here.

Although simple, the above class demonstrates key JAX-RS features. The most interesting JAX-RS pieces of the class are the JAX-RS annotations such as @Path, @GET, @Consumes, @Produces, and @PathParam. I won't delve into what these JAX-RS annotations do in this post because my emphasis is on using Jersey. See the Java EE 6 Tutorial chapter on REST with Jersey for more background on these annotations.

I will deploy the JAX-RS-annotated class to GlassFish in a WAR file with the appropriate web.xml file as shown next:

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>ServletAdaptor</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>ServletAdaptor</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>

<session-config>
<session-timeout>
30
</session-timeout>
</session-config>

</web-app>


In my case, NetBeans 6.8 generated this web.xml file for me automatically when I added the appropriate JAX-RS and Jersey JAR files to my project's libraries. This is a relatively simple web.xml file because of GlassFish's awareness of JAX-RS. (A strikingly similar web.xml works for deploying Jersey-based REST applications to Tomcat as demonstrated in Jason Drake's blog post Deploying Jersey in Tomcat 6.0.)

For my example, a WAR file called Rest1.war is generated. Its contents are displayed in the next screen snapshot.



As the screen snapshot indicates, there are JAX-RS and Jersey JAR files included in the generated WAR file. The class MovieRestApplication can be ignored because it is not used with Jersey on GlassFish. This means that the only custom files in the WAR are the JAX-RS-annotated class MovieOfTheDay, the web.xml file, and the index page (index.jsp). The content of the index.jsp page is shown next.

index.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>REST with JAX-RS Example</title>
</head>
<body>
<h1>REST Can be Easy!!!</h1>
</body>
</html>


The next screen snapshot demonstrates deployment of the generated WAR file via the web-based Glass Administrative Console:



The most important detail to note from the image of the deployment of the WAR file is that I have named the context root "rest." This will be part of the URIs by which my deployed REST services are accessed. The earlier web.xml file also showed that resources/ will be part of this REST service access URI as well. The remainder of the appropriate URI is based on the pieces of URI provided in the JAX-RS annotations on the Java class (/movies, /, and /{month}/{date}). The portions of URI denoted with curly braces indicate that the placeholders will be injected with values from the JAX-RS implementation that are in the calling URI. For example, if the relevant piece of URI was /7/4, this would indicate, in this case, a month of 7 (July because not using Java's zero-based month index in the URI) and a day of 4.

When deployment is successful, the administrative console appears as shown in the next screen snapshot.



With the JAX-RS application deployed, I can now access it from a myriad of different clients. JAX-RS does not spell out a standardized approach for clients, but Jersey and most other popular JAX-RS implementations provide their own approach for building clients. Other HTTP/REST clients are also available such as RESTClient. For now, I'll simply use a web browser.

Placing the URI http://localhost:8080/ in my browser displays the main page indicating that GlassFish is running:



If I add the web context (rest) to the URI, I see my index.jsp page:



To access the JAX-RS-powered REST applications, I need to add the resources portion of the URI as specified in the web.xml file. When I add this plus the /movies portion (as specified in the @Path annotation), I see the following page.



The above screen snapshot indicates that the GET access was invoked with the path "/" and the getMovie method was called. At this point, I can add a month and date to the URI to get a movie for that specific day. The next two screen snapshots demonstrate this for Groundhog Day and for Christmas Day.





As the above screen snapshots demonstrate, the months and days provided in the URIs are automatically injected by the JAX-RS provider into the parameters to the appropriate method. Now that's easy!


Conclusion

The process of deploying a JAX-RS-based web service using Jersey and GlassFish is relatively straightforward. All I really needed was access to the JAX-RS and Jersey JARs, the properly annotated Java class, and the short web.xml file that allowed Jersey to be used as a servlet. This blog post has attempted to show the basic steps involved with writing a simple JAX-RS-annotated class, deploying it to GlassFish, and taking advantage of Jersey's implementation of JAX-RS.


Other Resources

⇒ RESTful Web Services Developer's Guide

⇒ RESTful Java, Some Links

⇒ JSR 311: A Java API for RESTful Web Services?

⇒ Deploying and Testing a Jersey Application without NetBeans

⇒ Jersey 1.0: Getting Started

⇒ JSR-311 Javadoc-based API

Sunday, February 7, 2010

The Rise of Google Chrome

The Google Chrome web browser has continued to gain in popularity since most of us first heard about it. Daniel Cawrey's blog post w3schools: Chrome Has Surpassed Ten Percent Share points out that W3Schools's Browser Statistics Month by Month shows the Chrome browser being used for 10.8% of visits to that web site in January 2010. In that same month, Microsoft Internet Explorer accounted for 36.2% of visits (14.3% MSIE 8, 11.7% MSIE 7, and 10.2% MSIE 6) and Mozilla Firefox accounted for 46.3% of visits.

The numbers reported by W3Schools (especially the totals of 46.3% for Firefox, 36.2% for MSIE, and 10.8% for Chrome) are not exactly what one would expect to see in the general web browsing population and this difference reflects the specialized web development focus of the W3Schools audience. Knowing that the audience of people who would read my blog or come to it from a search engine would be similarly slanted in terms of which browsers were used, I was curious to see exactly which browsers are most popular for visitors to my blog. I used Google Analytics to determine the following numbers for viewers of my blog in January 2010: Firefox 57%, Chrome 20%, Internet Explorer 10%, Safari 5.6%, and several others picking up the remaining percentage. These numbers show that Firefox is by far the most dominant browser for people who access my blog and that Chrome makes a very impressive second-place appearance with 20% of the visits.

Neither the W3Schools nor my blog usage are indicative of the general web browsing population. NetMarketShare provides an estimate of more general browser usage and its January 2010 results show Internet Explorer still being the dominant browser by far with Firefox firmly in control of second place followed by Chrome and then Safari. According to NetMarketShare's numbers more than 4 out of every 5 browsers being used is Internet Explorer or Firefox.

The long-term implications of all this remain to be seen, but there are some short-term observations that can be made. Not surprisingly, Chrome and Firefox appear to be far more popular in the software development world (including Java development and web development) than in the general population. For web developers in particular, it has long been frustrating to deal with MSIE's being stubbornly non-standard in many different areas. However, because MSIE has dominated the general web user community, it has been important to write significant non-standard code to support that large set of users. Furthermore, Chrome continues to gain users in both the general public and in the software development communities.

Security concerns related to Microsoft Internet Explorer are not really new, but have recently received renewed focus, including the now frequently cited French and German governments' recommendations to abstain (temporarily) from using MSIE. In a blog post earlier this week called IE Flaw Makes Local Files Public, taranfx states, "The end of Internet Explorer is finally here" and supports this conclusion with a brief account of problems related to use of MSIE that are "forcing users to move to alternatives." This post also provides a link to the BlackHat DC 2010 white paper Abusing Insecure Features of Internet Explorer.

Chrome is not only slowing chipping away at MSIE's usage, but is also taking market share from Firefox. It still remains to be seen how popular Chrome can become. My preference would be for Firefox and Chrome to remain strong alternatives in the browser market. I don't see a lot of benefit from Chrome simply replacing Firefox as an alternative. The best scenario for web developers and web users would be for the browser wars to drive greater standardization at a faster pace.

Saturday, February 6, 2010

Oracle Enterprise Pack for Eclipse

The Oracle Enterprise Pack for Eclipse (OEPE) is described as a "certified set of Eclipse plug-ins is designed to help develop, deploy and debug applications for Oracle WebLogic Server." Although this is true, it offers much more than that with support for development with the Spring Framework, the Java Persistence API (JPA) and/or other object-relational mapping technologies, and other enterprise development in conjunction with its support for WebLogic development and deployment. My article on using OEPE to improve development with Spring, JPA, and WebLogic was published this week on Oracle Technology Network. The article, called Build a Java Application with Eclipse, Spring, and Oracle WebLogic Server, covers the convenient features of OEPE for Spring and JPA developers, especially (but not necessarily) those who deploy to WebLogic.

There are several links on DZone related to the recent release of a new version of Oracle Enterprise Pack for Eclipse. These include Oracle Enterprise Pack for Eclipse 11gR1 (11.1.1.4) Release Webinar (linked article) and Oracle Upgrades Eclipse Free Plug-in Package (linked article). The EclipseZone portion of DZone has several additional articles devoted to OEPE such as the article Oracle Enterprise Pack for Eclipse (an interview with Ashish Mohindroo)

Monday, February 1, 2010

Basic Java hashCode and equals Demonstrations

I often like to use this blog to revisit hard-earned lessons in the basics of Java. This blog post is one such example and focuses on illustration of the dangerous power behind the equals(Object) and hashCode() methods. I won't cover every nuance of these two highly significant methods that all Java objects have whether explicitly declared or implicitly inherited from a parent (possibly directly from Object itself), but I will cover some of the common issues that do arise when these are not implemented or are not implemented correctly. I also attempt to show by these demonstrations why it is important for careful code reviews, thorough unit testing, and/or tool-based analysis to verify the correctness of these methods' implementations.

Because all Java objects ultimately inherit implementations for equals(Object) and hashCode(), the Java compiler and indeed the Java runtime launcher will report no problem when invoking these "default implementations" of these methods. Unfortunately, when these methods are needed, the default implementations of these methods (like their cousin the toString method) are rarely what are desired. The Javadoc-based API documentation for the Object class discusses the "contract" expected of any implementation of the equals(Object) and hashCode() methods and also discusses the likely default implementation of each if not overridden by child classes.

For the examples in this post, I'll be using the HashAndEquals class whose code listing is shown next to process object instantiations of various Person classes with differing levels of support for hashCode and equals methods.

HashAndEquals.java
package dustin.examples;

import java.util.HashSet;
import java.util.Set;
import static java.lang.System.out;

public class HashAndEquals
{
private static final String HEADER_SEPARATOR =
"======================================================================";

private static final int HEADER_SEPARATOR_LENGTH = HEADER_SEPARATOR.length();

private static final String NEW_LINE = System.getProperty("line.separator");

private final Person person1 = new Person("Flintstone", "Fred");
private final Person person2 = new Person("Rubble", "Barney");
private final Person person3 = new Person("Flintstone", "Fred");
private final Person person4 = new Person("Rubble", "Barney");

public void displayContents()
{
printHeader("THE CONTENTS OF THE OBJECTS");
out.println("Person 1: " + person1);
out.println("Person 2: " + person2);
out.println("Person 3: " + person3);
out.println("Person 4: " + person4);
}

public void compareEquality()
{
printHeader("EQUALITY COMPARISONS");
out.println("Person1.equals(Person2): " + person1.equals(person2));
out.println("Person1.equals(Person3): " + person1.equals(person3));
out.println("Person2.equals(Person4): " + person2.equals(person4));
}

public void compareHashCodes()
{
printHeader("COMPARE HASH CODES");
out.println("Person1.hashCode(): " + person1.hashCode());
out.println("Person2.hashCode(): " + person2.hashCode());
out.println("Person3.hashCode(): " + person3.hashCode());
out.println("Person4.hashCode(): " + person4.hashCode());
}

public Set addToHashSet()
{
printHeader("ADD ELEMENTS TO SET - ARE THEY ADDED OR THE SAME?");
final Set set = new HashSet();
out.println("Set.add(Person1): " + set.add(person1));
out.println("Set.add(Person2): " + set.add(person2));
out.println("Set.add(Person3): " + set.add(person3));
out.println("Set.add(Person4): " + set.add(person4));
return set;
}

public void removeFromHashSet(final Set sourceSet)
{
printHeader("REMOVE ELEMENTS FROM SET - CAN THEY BE FOUND TO BE REMOVED?");
out.println("Set.remove(Person1): " + sourceSet.remove(person1));
out.println("Set.remove(Person2): " + sourceSet.remove(person2));
out.println("Set.remove(Person3): " + sourceSet.remove(person3));
out.println("Set.remove(Person4): " + sourceSet.remove(person4));
}

public static void printHeader(final String headerText)
{
out.println(NEW_LINE);
out.println(HEADER_SEPARATOR);
out.println("= " + headerText);
out.println(HEADER_SEPARATOR);
}

public static void main(final String[] arguments)
{
final HashAndEquals instance = new HashAndEquals();
instance.displayContents();
instance.compareEquality();
instance.compareHashCodes();
final Set set = instance.addToHashSet();
out.println("Set Before Removals: " + set);
//instance.person1.setFirstName("Bam Bam");
instance.removeFromHashSet(set);
out.println("Set After Removals: " + set);
}
}


The class above will be used as-is repeatedly with only one minor change later in the post. However, the Person class will be changed to reflect the importance of equals and hashCode and to demonstrate how easily it can be to mess these up while at the same time being difficult to track down the problem when there is a mistake.


No Explicit equals or hashCode Methods

The first version of the Person class does not provide an explicit overridden version of either the equals method or the hashCode method. This will demonstrate the "default implementation" of each of these methods inherited from Object. Here is the source code for Person without hashCode or equals explicitly overridden.

Person.java (no explicit hashCode or equals method)
package dustin.examples;

public class Person
{
private final String lastName;
private final String firstName;

public Person(final String newLastName, final String newFirstName)
{
this.lastName = newLastName;
this.firstName = newFirstName;
}

@Override
public String toString()
{
return this.firstName + " " + this.lastName;
}
}


This first version of Person does not provide get/set methods and does not provide equals or hashCode implementations. When the main demonstration class HashAndEquals is executed with instances of this equals-less and hashCode-less Person class, the results appear as shown in the next screen snapshot.



Several observations can be made from the output shown above. First, without explicit implementation of an equals(Object) method, none of the instances of Person are considered equal, even when all attributes of the instances (the two Strings) are identical. This is because, as is explained in the documentation for Object.equals(Object), the default equals implementation is based on an exact reference match:

The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).


A second observation from this first example is that the hash code is different for each instance of the Person object even when two instances share the same values for all of their attributes. The HashSet returns true when a "unique" object is added (HashSet.add) to the set or false if the added object is not considered unique and so is not added. Similarly, the HashSet's remove method returns true if the provided object is considered found and removed or false if the specified object is considered to not be part of the HashSet and so cannot be removed. Because the equals and hashCode inherited default methods treat these instances as completely different, it is no surprise that all are added to the set and all are successfully removed from the set.


Explicit equals Method Only

The second version of the Person class includes an explicitly overridden equals method as shown in the next code listing.

Person.java (explicit equals method provided)
package dustin.examples;

public class Person
{
private final String lastName;
private final String firstName;

public Person(final String newLastName, final String newFirstName)
{
this.lastName = newLastName;
this.firstName = newFirstName;
}

@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (this == obj)
{
return true;
}
if (this.getClass() != obj.getClass())
{
return false;
}
final Person other = (Person) obj; 
if (this.lastName == null ? other.lastName != null : !this.lastName.equals(other.lastName))
{
return false;
}
if (this.firstName == null ? other.firstName != null : !this.firstName.equals(other.firstName))
{
return false;
}
return true;
}

@Override
public String toString()
{
return this.firstName + " " + this.lastName;
}
}


When instances of this Person with equals(Object) explicitly defined are used, the output is as shown in the next screen snapshot.



The first observation is that now the equals calls on the Person instances do indeed return true when the object is equal in terms of all attributes being the same rather than checking for a strict reference equality. This demonstrates that the custom equals implementation on Person has done its job. The second observation is that implementation of the equals method has had no effect on the ability to add and remove the seemingly same object to the HashSet.


Explicit equals and hashCode Methods

It is now time to add an explicit hashCode() method to the Person class. Indeed, this really should have been done when the equals method was implemented. The reason for this is stated in the documentation for the Object.equals(Object) method:

Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.


Here is Person with an explicitly implemented hashCode method based on the same attributes of Person as the equals method.

Person.java (explicit equals and hashCode implementations)
package dustin.examples;

public class Person
{
private final String lastName;
private final String firstName;

public Person(final String newLastName, final String newFirstName)
{
this.lastName = newLastName;
this.firstName = newFirstName;
}

@Override
public int hashCode()
{
return lastName.hashCode() + firstName.hashCode();
}

@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (this == obj)
{
return true;
}
if (this.getClass() != obj.getClass())
{
return false;
}
final Person other = (Person) obj; 
if (this.lastName == null ? other.lastName != null : !this.lastName.equals(other.lastName))
{
return false;
}
if (this.firstName == null ? other.firstName != null : !this.firstName.equals(other.firstName))
{
return false;
}
return true;
}

@Override
public String toString()
{
return this.firstName + " " + this.lastName;
}
}


The output from running with the new Person class with hashCode and equals methods is shown next.



It is not surprising that the hash codes returned for objects with the same attributes' values are now the same, but the more interesting observation is that we can only add two of the four instances to the HashSet now. This is because the third and fourth add attempts are considered to be attempting to add an object that was already added to the set. Because there were only two added, only two can be found and removed.


The Trouble with Mutable hashCode Attributes

For the fourth and final example in this post, I look at what happens when the hashCode implementation is based on an attribute that changes. For this example, a setFirstName method is added to Person and the final modifier is removed from its firstName attribute. In addition, the main HashAndEquals class needs to have the comment removed from the line that invokes this new set method. The new version of Person is shown next.

package dustin.examples;

public class Person
{
private final String lastName;
private String firstName;

public Person(final String newLastName, final String newFirstName)
{
this.lastName = newLastName;
this.firstName = newFirstName;
}

@Override
public int hashCode()
{
return lastName.hashCode() + firstName.hashCode();
}

public void setFirstName(final String newFirstName)
{
this.firstName = newFirstName;
}

@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (this == obj)
{
return true;
}
if (this.getClass() != obj.getClass())
{
return false;
}
final Person other = (Person) obj; 
if (this.lastName == null ? other.lastName != null : !this.lastName.equals(other.lastName))
{
return false;
}
if (this.firstName == null ? other.firstName != null : !this.firstName.equals(other.firstName))
{
return false;
}
return true;
}

@Override
public String toString()
{
return this.firstName + " " + this.lastName;
}
}


Output generated from running this example is shown next.



The most interesting observation in this example is that although two instances get added to the set, only one gets removed. This is because one of the attributes upon which the hash code is based, first name, changes in between adding the object to the HashSet and attempting to remove the same object (albeit with a changed first name attribute) from the same HashSet. This illustrates the importance of implementing hashcode (and by extension, equals) on immutable values. More details regarding this can be found in blog posts HashSet.contains(): does your busket contain something? (original location) and Back to hashCode Mutability (original location).


Detecting Problems Related to hashCode and equals Implementations

As the examples in this post demonstrate, different behaviors occur depending on how these methods are implemented, but none of them involve an obvious error or warning. This can make it difficult to track down seemingly inconsistent or strange behavior. The best way to address this is with careful implementation of these methods, careful reviews of these important methods, and thorough testing. Another useful tactic is to avoid mindless creation of unnecessary "set" methods for all of a class's data attributes. If a "set" method is truly appropriate, that attribute should not be used in the implementation of equals or hashCode.


Conclusion

It is almost never appropriate to rely on an object's default implementation of hashCode and equals as inherited from Object. Furthermore, the equals and hashCode methods should be implemented to the contract advertised in the Javadoc documentation (only a small part of which was covered here) and should not be based on any attributes that will be changed (mutable) during the lifecycle of the instance.