Top 10 Nasty Java Bugs

By Jim White (Director of Training and Instructor)

Every software developer deals with bugs. The really tough bugs are the ones not detected by the compiler. Nasty bugs manifest themselves only when executed at runtime. You know – the types of bugs that sometimes take hours and another set of eyes to find.  Here is my list of the top ten difficult and common Java bugs. Even though these bugs are specific to Java, most of them would apply to just about any object-oriented language. Knowing and avoiding these can reduce hours spent testing and debugging applications.

1. No no-argument constructor. Java provides a no-argument constructor to each class by default (a.k.a. the default constructor). However, as soon as you add your own constructor, the compiler no longer includes the default/no-argument constructor in your class. Compilers detect direct calls to use a non-existent no-argument constructor. However, a nasty bug comes to life when you use popular frameworks like Spring or Hibernate that rely on the existence of a no-argument constructor to create instances of your classes and you forget to provide one in the class. Since these frameworks often rely on reflection rather than a direct call to the no-argument constructor, compilers are not able to detect the potential issue. Instead, you encounter a runtime exception when executing code that involves the class without the no-argument constructor and the framework (as shown in this example with Spring below).

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘contact1′ defined in file [C:\student\workspace\ContactManagement\test-beans.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.intertech.domain.Contact]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.intertech.domain.Contact.<init>()

2. == versus equals( ). In Java, there are two forms of equal with objects. The == operator checks to see if two object references point to the same object in memory (in heap). The equals( ) method, can and often does operate differently than the == operator. For example, when using equals( ) with String objects, Java is checking that the objects contain the same sequence of characters. Using the wrong form of ‘equal’ in conditionals can result in a bug that does not manifest itself in any immediate exception. Usually, this type of bug is detected only after carefully stepping through the code when getting unexpected application results.

String x = “a test”;
String y = new String(“a test”);
System.out.println(x==y); //prints false
System.out.println(x.equals(y)); //prints true

3. JDBC is 1-based. When you learn Java, you quickly commit to memory the fact that Java is a zero-based indexing language. For example, to get the first element from an array or collection, you ask to ‘get’ the item at the index of zero (0). The JDBC API, however, is one-based. Setting statement parameters, getting column data by index in result sets, etc. all start at index one (1). Depending on the situation, code that uses the wrong index may throw an SQL Exception at runtime or create a hidden data issue caught only by closely exploring the data (which may now be corrupted) in the database.

4. NullPointerExceptions. Unfortunately, Java compilers can sometimes warn us but not protect us from uninitialized variables, instance variables and the like. Trying to perform operations on a property, for example, that has not been initialized and points to null results in the most infamous and common runtime exception: NullPointerException.  Java provides many ways to initialize vairables (explicit initialization, init block, constructors, etc.).  Use one of them to provide default values or references for variables.

Contact c = new Contact();
//results in a null pointer exception if getFirstName returns null
String name = c.getFirstName().toLowerCase();

5. Empty Catch Blocks. Bugs are tough enough to track down without hiding exceptions that are occasionally thrown by code. Developers can be lazy and when roughing out a piece of code, some leave the catch block of try/catch code empty. An ‘I’ll-get-that-later’ mentality can end up masking a bug for a very long time. At the very least, log the exception in the catch block. While developing, if you must leave the exception handling coding for another day, consider having the catch block throw an unchecked exception. This will at least cause problems to manifest themselves at the point of origin and ultimately force you to deal with that unhappy path in your code.

6. Shadowing Attributes. Local variables can have the same name as instance or class variables. Often, this is done to help highlight how a method parameter is going to be used to set the value of an instance or class variable (see below). We call this shadowing attributes. However, mistakes in forgetting the ‘this’ keyword, or forgetting that the local variable shares the name of an instance or class variable can escape compiler detection and cause all sorts of issues (from NullPointerExceptions to hidden data issues) at runtime.  Attribute shadowing can help clarify how a variable is to be used, but don’t let it obfuscate a but.

//good shadowing
public void setFirstName(String firstName) {
  this.firstName = firstName;
}
// Hidden problem. Now the firstName instance variable is never set.
public void setFirstName(String firstName) {
  firstName = firstName;
}

7. Same Name, Different Package. At the airport baggage claim, you’ll often find a sign that reads, ‘Many bags look alike.’ The connotation is that you should check the bag you pick out to make sure it is really yours before you leave the baggage claim area. The same can be said of classes and interfaces. Many classes/interfaces have the same name. Sometimes they even perform many of the same operations (especially in the case of different versions of a framework, org.hibernate.Session and org.hibernate.classic.Session is a perfect example). When you pick the wrong class or interface, the problems may not be caught until runtime. Because the classes may serve the same or similar roles, the runtime exceptions that occur might even mislead you to believe the issue is somewhere else. Always review package import statements, especially when using IDEs that may automatically add them to your code.

8. Memory Leaks. Yes, you can have memory leaks in Java even though Java has automatic garbage collection. In order for the garbage collection to work, it must be able to identify an object as garbage. If you create a long-standing reference to an object, it will not get collected as garbage despite the fact that the object is no longer used. In the extreme, consider the small piece of code below. Message objects are collected for as long as messages are coming in, which might be forever! However, the Message objects are probably not used after work with that Message is complete. The messages never get removed from the currentMessages hash and therefore never avail themselves for garbage collection. How long will it take for the machine running this code to run out of memory? The answer depends, but no machine can hold objects forever and ever.

HashSet currentMessages = new HashSet();
while (moreMessages()){
  Message message = getNextMessage();
  currentMessages.add(message.getID(), message);
  //do work with message
}

Memory leaks can hide themselves for months or minutes depending on the runtime environment. In a server loaded with memory, it may take months for a memory leak like the one shown to manifest. In a Java ME device where memory is limited, an OutOfMemoryError is likely seen much sooner. Watch object references closely and remember to dereference an object when you are done with it. Tools, such as JProbe, can also help profile your applications and help identify memory leaks before they become a problem in production environments.

9. Concurrent Access to Shared Data. Developers often test their code by running their code and acting as a single user. It is tough, but absolutely critical, to simulate multiple, simultaneous users when testing code. Data comes in many forms. Data can be persisted to a relational database. Data can also be held in memory in the form of instance variables. No matter the data type or location, make sure all data is protected from multiple users hitting it simultaneously. Concurrent data access issues hardly ever result in exceptions thrown, but almost always end up corrupting data, a far worse problem. As a small example, consider the Java servlet below. Because Web containers only create a single instance of each servlet, and run many user threads through the same instance, the name instance variable here is open to concurrent updates. Testing this application as a single user will never highlight this potentially grave error. Even when there are tens of users, a problem with the code may not be seen or seen rarely. Concurrent access of shared data must be examined as part of the application design. It requires an understanding of what is shared and how to protect it given the type and location of the data.

public class NonConcurrentServlet extends HttpServlet
{
  String name;
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    PrintWriter out = response.getWriter();
    response.setContentType(“text/html”);
    userName = request.getParameter(“name”);
    out.println(“<html><body>”);
    out.println(“<h1>Hello, “+name+”</h1>”);
    out.println(“</body></html>”);
  }
}

10. ClassCastExceptions. Polymorphism is a core principal in object-oriented programming languages like Java. It allows code to be very flexible. Interfaces, for example, establish a contract that can be implemented by many classes. In many situations, the compiler checks our code and prevents us from creating references to the wrong type of object. However, cast operations (whether implicit or explicit) can create situations that allow the compiler to believe that you have appropriately typed references to objects when you really have not. When the JVM hits an unexpected object type, it throws the dreaded runtime ClassCastException. These types of runtime exceptions can be more prevalent when using frameworks like Spring, Hibernate, etc. where Java code is at the mercy of XML or other configuration that dictates the type of object actually created.

FileSystemXmlApplicationContext context =
new FileSystemXmlApplicationContext(“spring-beans.xml”);
// is the salescontacts bean really going to be a ContactList??
ContactList list = (ContactList) context.getBean(“salescontacts”);

Wrap up

If you are a new or experienced Java developer, keep this list handy and run through it as you get close to shipping your code. Make sure you have taken your best shot at eliminating those nasty bugs that may appear only after your code is getting run extensively. Need some help with understanding these issues and how to be a better developer? Take a look at Intertech’s curriculum. Give us a chance to improve your ‘bug-eyes.’

If you have been doing Java for some time and might even be in the ‘Guru’ category, how does my list stack up? Feel free to comment and leave us your thoughts on what needs to be added/removed from the list.

{Offer-Title}

{Offer-PageContent}
Click Here