Find the Java Bugs That Pose a Threat with FindBugs

indBugs is an open-source static analysis tool designed to find bugs in your Java code. Unlike many other static analysis tools, however, FindBugs concentrates almost exclusively on isolating potentially dangerous coding errors that could break your application. This powerful tool can find subtle yet dangerous bugs that other static analysis tools will not detect.

FindBugs works by searching compiled Java bytecode for what it calls “bug patterns.” Simply put, a bug pattern is a coding practice that often leads to bugs. Bug patterns are based on the observation that people tend to reproduce the same errors, both individually and collectively. Humans are creatures of habit, and habits tend to persist—even harmful ones. Inexperienced developers often make the same mistakes that more experienced developers were making when they were newbies. So, unless a conscious effort is made to identify and correct a poor programming habit, it will raise its head again and again. In particular, trickier parts of the Java language APIs can lead to common mistakes.

FindBugs comes with over 300 bug patterns (336 in FindBugs 1.3.4), organized in different bug categories, such as “Malicious Code,” “Bad Practice,” and “Performance.” Each bug also has a priority rating (high, medium, and low). You can use this rating system to prioritize your bug-correction activities. The FindBugs team recommends treating both high- and medium-priority issues seriously.

In addition to isolating the most dangerous of these poor coding habits and mistakes, FindBugs also explains in great detail what is wrong with the code. This feature makes FindBugs not only an effective bug detector but also an excellent learning tool: it can help hone a team’s programming skills and help individual developers avoid similar mistakes in the future.

Coding standards are generally recognized as a good thing, and any tool that helps you enforce your organization’s own coding conventions is certainly worthwhile. While many coding best practices focus on making your code more readable and easier to maintain, FindBugs has a very different focus: finding nasty bugs and subtle errors that will crash your application, often at the worst possible time.

This article briefly introduces FindBugs, showing in particular how to use it in Eclipse, and then demonstrates its features with a few examples.

What You Need
Java 5
Lots of memory (at least 512MB are recommended)

Using FindBugs

Figure 1. The FindBugs Graphical Tool: Use the default FindBugs graphical tool to review the issues that FindBugs raises.

Despite its power, FindBugs is easy to use. In fact, you can run it against your code in several ways. One way is to use the default FindBugs graphical tool, and review the issues that FindBugs raises (see Figure 1). This tool also lets you classify issues into categories such as “Should Fix” and “Mostly Harmless,” and sort issues in various ways.

However, a more convenient way to use FindBugs is directly within your IDE. This way, FindBugs can analyze your code and isolate issues within your normal development environment, where you can fix them immediately.

Figure 2. Using FindBugs in Eclipse: FindBugs provides an Eclipse plugin that integrates FindBugs directly into your Eclipse environment.

To this end, FindBugs provides an Eclipse plugin that integrates FindBugs directly into your Eclipse environment (see Figure 2). You can install this plugin using the FindBugs Update site. Once installed, you can run the FindBugs analysis against your code using the “FindBugs” entry in the contextual menu.

Figure 3. Configuring FindBugs in Eclipse: You can fine-tune FindBugs behavior in the “FindBugs” entry in the “Project->Properties” window.

FindBugs issues are easy to see—just look for the red insect icon in the margin of the source code window. A nice touch is that the color of the insect indicates severity—the darker the red, the more severe the issue. You can pass the mouse over the issue to get a quick overview of the problem. You can also list FindBugs issues in the “Problems” view, or in one of the specialized FindBugs views. If you click on a particular FindBugs issue, Eclipse will open the FindBugs Details view, which contains the detailed description of the bug.

Once you become more familiar with FindBugs, you can fine-tune its behavior in the “FindBugs” entry in the “Project->Properties” window (see Figure 3). For example, you can configure FindBugs to run automatically or deactivate certain FindBugs rules if they don’t suit your particular needs. The “Reporter Configuration” tab lets you filter out entire categories of rules, whereas the “Filter files” tab lets you filter out certain files or file sets from the FindBugs analysis.

FindBugs in Practice: Beyond Checkstyle and PMD
FindBugs is just one of many available static analysis tools, both open source and commercial. Two other commonly used static analysis tools in the Java arena are Checkstyle and PMD. Checkstyle has traditionally focused on coding standards such as naming conventions and spacing, and the presence of Javadocs. PMD is more focused on best practices, sub-optimal code, and potential errors. Some overlap inevitably exists between static analysis tools, especially between Checkstyle and PMD and, to a lesser extent, between PMD and FindBugs.

To get a better idea of the areas in which FindBugs shines, consider a practical example. Suppose you have to implement a database of registered pet dogs for a government agency. The following class represents the main domain object, the pet dog:

public class Dog {	private String name;	private String breed;		// Getters and setters	//...}

Now suppose that at some point during the project, you need to implement the equals() method (for example, you need to place Dog instances in a set). However, correctly implementing the equals() method can be a tricky operation, especially for novice programmers. Here is a relatively poor implementation of the equals() method for the Dog class:

public class Dog {    private String name;    private String breed;    @Override    public boolean equals(Object obj) {        Dog otherDog = (Dog) obj;        if ((otherDog.name.equals(this.name))            && (otherDog.breed.equals(this.breed))) {            return true;        }        return false;    }}

This implementation contains several mistakes, coding errors that could lead the application to crash or behave in unexpected ways. In particular, if the specified object is not an instance of the Dog class, the method will through a ClassCastException. There is no test to make sure the object is not null. And, although the equals() method has been overridden, there is no matching overridden hashCode() implementation. The Java language requires equal objects to return equal hashcode values. If they don’t, HashMaps and HashTables are likely to behave incorrectly. So, whenever you override the equals() method, you should also override the hashCode() method. This rule, well known to veteran Java developers, is often overlooked by less experienced developers.

The implementation also shows a few poor coding practices, such as the lack of a Javadoc comment.

Each of the static analysis tools mentioned above will raise different issues for this code. For example, Checkstyle will indicate the following errors:

  1. The method is missing a Javadoc comment.
  2. The parameter obj should be final (as it is not modified in the method).
  3. There is a definition of equals() without a corresponding definition of hashCode().

Of these errors, the first is a coding standards issue, the second is a coding best practice, and the last is a potential bug as well as a well-known best practice. This error report reflects the general emphasis that Checkstyle places on enforcing coding standards and good programming habits.

PMD is more focused on best practices. When you run PMD against this code, it will raise the following issues:

  1. “A method should have only one exit point, and that should be the last statement in the method.”
  2. “Ensure that you override both equals() and hashCode().”
  3. “Parameter obj is not assigned and could be declared final.”

Of these three issues, two were also raised by Checkstyle. The issue raised only by PMD in this case concerns a common Java coding best practice: limit the number of return statements in a method in order to reduce the complexity of the code. This exemplifies PMD’s focus on best practices. PMD is not concerned by the lack of Javadoc comments, nor would it worry about method or variable naming conventions, spacing and indentation, or other pure coding standards issues.

Now take a look at what FindBugs comes up with. Like the other tools, FindBugs raises three issues with this code, but their nature is quite different from those raised by the other two tools:

  1. The equals method should not assume anything about the type of its argument.
  2. The class defines equals() and uses Object.hashCode().
  3. The equals() method does not check for null arguments.

The first error finds one of the major flaws in the design of this equals() method: if the argument is not an instance of the Dog class, the method will fail. FindBugs’ detailed description of this issue can also help explain both the problem and the solution:

The equals(Object o) method shouldn’t make any assumptions about the type of o. It should simply return false if o is not the same type as this.

The second issue is the same equals()/hashCode() issue raised by the other two tools. However, the FindBugs description of this issue goes much further in explaining the reasons behind the problem and how to fix it:

This class overrides equals(Object), but does not override hashCode(), and inherits the implementation of hashCode() from java.lang.Object (which returns the identity hash code, an arbitrary value assigned to the object by the VM). Therefore, the class is very likely to violate the invariant that equal objects must have equal hashcodes.If you don’t think instances of this class will ever be inserted into a HashMap/HashTable, the recommended hashCode implementation to use is:

	public int hashCode() {  		assert false : "hashCode not designed";  		return 42; // any arbitrary constant will do   	}"  

The third issue is another potential bug that only FindBugs detected: the method fails to check for a null value in the parameter. The issue raised by FindBugs is actually even more precise than the error message indicates, and the description both details the problem and suggests a simple solution:

This implementation of equals(Object) violates the contract defined by java.lang.Object.equals() because it does not check for null being passed as the argument. All equals() methods should return false if passed a null value.

This simple example illustrates two major differences that distinguish FindBugs from other static analysis tools:

  • FindBugs concentrates almost exclusively on coding issues that may result in application bugs.
  • FindBugs usually couples each issue it finds with a clear and detailed description, which can be used both to help understand the issue and to fix the problem.

Consider another example:

    public void foo() {       List values = null;       List results = new ArrayList();       for (String str : values) {           int value = Integer.parseInt(str);	     results.add(new Integer(value));       }    }

In this case, Checkstyle will not raise any significant issues, other than the lack of a Javadoc comment. Unless you configure Checkstyle carefully, it will also complain about the lack of spaces around the generic ‘<‘ and ‘>‘ operators. As per usual, Checkstyle is concentrating on the coding style and layout.

Figure 4. FindBugs Analysis in Action: A null pointer deference for the ‘values’ variable results in a NullPointerException during execution.

The default configuration of PMD will raise six issues, mostly to do with performance optimizations, including:

  1. The variables values, results, and value could be final. (This is theoretically correct, but rarely done in practice.)
  2. Avoid instantiating variables within loops. (This is a legitimate concern.)
  3. Found ‘DU’-anomaly for variable results. (This is somewhat obscure.)

FindBugs, on the other hand, raises only two issues:

  1. There is a null pointer deference for the values variable. This would result in a NullPointerException during execution (see Figure 4).
  2. The use of new Integer(value) is inefficient, and should be replaced by Integer.valueOf(value), which avoids the creation of unnecessary objects (A potential performance issue similar to the second issue raised by PMD).

Again, this illustrates FindBugs’ tendency to focus on potential bugs.

All static analysis tools raise false alerts. However, in practice, a high proportion of the issues raised by FindBugs turn out to be real bugs—often more than half, in my experience.

FindBugs in the Build Process
Using FindBugs directly in your IDE is probably the most efficient way to work with the static analysis tool. However, you can also integrate FindBugs bug detection very effectively into your build process. This is a great way to maintain high code quality across an entire project automatically and to make sure everybody plays by the rules. In addition, if you practice automated project-wide FindBugs analysis in conjunction with informal code reviews and/or regular code quality meetings, it can also be a great way to improve your team’s skills.

To this end, FindBugs integrates nicely with both Ant and Maven. If your build scripts are written using Ant, you can use the Ant task provided with the FindBugs distribution. Copy the findbugs-ant.jar file into your Ant lib directory, and declare the FindBugs task as follows:

You also need to define where FindBugs is installed on your machine. For example, if you installed it in a directory called findbugs under your home directory, you could do this:

Then you simply invoke the FindBugs task as follows:

                

This will produce an XML report containing details of all the FindBugs issues, suitable for machine consumption. For a more readable equivalent, you can use the HTML output option.

If you use Maven, all you need to do is integrate the Maven FindBugs plugin into the reporting section of your pom.xml file:

        ...              org.codehaus.mojo        findbugs-maven-plugin                  Normal          true                    ...      
Figure 5. FindBugs Analysis Statistics in Hudson: The Hudson Continuous Integration server is a user-friendly way to display FindBugs results and statistics.

Both these plugins can generate reports in both XML and HTML. The HTML reports are clean and readable, and can be used to publish your results. However, one of the most user-friendly ways to display FindBugs results and statistics is to use the Hudson Continuous Integration server (see Figure 5). Hudson takes the XML reports generated by FindBugs and produces an HTML report and annotated source code detailing the issues found in each build. It also produces a graph tracing the number of FindBugs issues found over time.

Zeroing in on Application-Threatening Bugs
In a market full of Java static analysis tools, FindBugs stands out. Rather than focusing on coding style, naming conventions, or best practices, FindBugs concentrates on identifying genuine application-threatening bugs. And it does so with a very reasonable degree of success—in my experience, at least half of the issues raised by FindBugs deserved attention. If you are serious about detecting errors in your code, FindBugs is a must.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: