Save yourself from Brittle Strings!

Over the last week, I completed a training course on Enterprise Java Beans (EJB) and the Java Persistence API (JPA). We are hoping to utilize this technology at work, and my employer offered myself and fellow co-workers the opportunity to participate in a week long class that reviews the technology from top to bottom. The class was really interesting, and I am very excited to utilize this technology in development. Our instructor was from an outside company called Webucator, and presented the material very well.

EJB and JPA make many of the necessities of real-time enterprise application development – such as database access, transaction processing, or job scheduling – much easier. Many of the concepts in EJB and JPA require annotations¬†on the code to accomplish tasks. If you’ve never seen an annotation before in Java, a simple example is shown below. The @Override annotation indicates that the method is overriding an implementation in a base class that is being extended:

public void foo()
	//Code removed

Many annotations happen to also take parameters to determine the behavior. One such parameter is JPA’s @NamedQuery annotation. This annotation allows us to create an object query using the Java Persistence Query Language (JPQL) and name it. Take a look at one example below, which finds MusicItem objects based on a keyword parameter:

@NamedQuery(name = "MusicItem.findByKeyword", query = "SELECT i "
		+ "FROM MusicItem i "
		+ "WHERE i.title LIKE :keyword "
                + "OR i.artist LIKE :keyword")

Now this statement looks all well and good at first glance. The syntax is correct, and the query is ready to be utilized by an application developer. However, there is something very dangerous!

Put yourself in the shoes of the engineer utilizing this query. You write a line of code like the following in your bean implementation class:

//The variable em is our EntityManager
em.createNamedQuery("MusicItem.findByKeyWord", MusicItem.class)
.setParameter("keyword", wildCardedKeyword).getResultList();

You deploy your newly coded bean to your application server, try to run it, and it blows up! Why is this? Take a close look at that query above again. The first parameter in EntityManager.createNamedQuery() is the name of the query. Notice that it isn’t exactly the same as what we put in our @NamedQuery annotation? In our annotation defining the query, we named it "MusicItem.findByKeyword", but in our bean class, we called it using "MusicItem.findByKeyWord". The case was not the same. This is an error that is tough to track down as it wouldn’t be exposed until the code is actually used. How can we avoid these kinds of problems?

Throughout the week, our instructor in the class kept referring to strings as very “brittle,” and that we need to be very careful when relying on them during development. This is something I never thought about until I got to my job working at Dematic – in all of my previous experiences (academic and professional), using strings was never discouraged. There is a solution to this issue – define your string in a constants class.

In our workplace, we have taken to developing constants classes for our projects. This is a very simple class that contains only public static final values to be utilized elsewhere in the project. Let’s consider the example of the @NamedQuery again, however this time we’ve also put a class called Constants in our project that looks as follows:

package com.prj;

public class Constants { public static final String QUERY_FIND_BY_KEYWORD_NAME= "MusicItem.findByKeyword"; public static final String QUERY_FIND_BY_KEYWORD_CONTENTS= "SELECT i " + "FROM MusicItem i " + "WHERE i.title LIKE :keyword " + "OR i.artist LIKE :keyword"; }

We’ve now defined two constants in our project, QUERY_FIND_BY_KEYWORD_NAME, which is the name of the query, and QUERY_FIND_BY_KEYWORD_CONTENTS, which is the query itself. Let’s try creating our @NamedQuery annotation again, this time with these constants:

//Assume all imports are done correctly
@NamedQuery(name = Constants.QUERY_FIND_BY_KEYWORD_NAME, 
	    query = Constants.QUERY_FIND_BY_KEYWORD_CONTENTS)

Doesn’t that look much cleaner? And now for our call to the EntityManager.createNamedQuery() method in our bean implementation:

MusicItem.class).setParameter("keyword", wildCardedKeyword)

Now, we are guaranteed to avoid the runtime error we faced earlier! With a hard-coded string in a constants file, our engineer utilizing the query can’t possibly mistype the name of the query.

This is just one of the many benefits of utilizing hard-coded strings in your development practice. There are many other benefits as well. Constants are easy to change/adjust if needed, the variable names are easy to refactor, and they are easy to search for all instances of when using an IDE. If you aren’t using hard-coded constants in a central location as part of your development practice, I would highly recommend starting. It will make your life much easier and save you hours of time in the long run.