Back to Basics: The Perfect Java Bean Property

Bean properties were defined as part of the JavaBean standard, but they are used most often on "POJOs" with nary a java.bean.* import to be seen. The use of the bean property naming standards as the foundations for frameworks such as Hibernate, Spring and many others demonstrates the power of their zen-like simplicity. The implementations of many bean properties, however, leave much to be desired.

Perfection is more of an aspiration than an assertion. The following is perhaps a small step towards bean property enlightenment, and demonstrates some techniques for locking down your bean properties to help make your system as a whole less bug prone from the bottom up.

Overview

Typical Bean Properties

In most Java projekts, you'll find lots of bean property code like this:


  public class MyBean {

    private String m_name;

    public String getName() {
      return m_name;
    }

    public void setName(String name) {
      return m_name;
    }

    ...

  }

Bean property implementations like this really don't do anything other than provide access to a field via the bean property naming conventions. You might as well have your field public and just access it directly.

Bean properties should be more than just a naming convention layered over a private field. Bean property code has a responsibility to jealously guard the field, only allowing valid values to be set, and to only provide valid values back to the caller. The collection of code that makes up the bean property should be robust enough to sensibly handle, and reject where appropriate, any value that the caller might throw at it. If an objects bean properties aren't robust, then how can the object and the rest of your system be robust?

For example, consider the following code which calls the bean property defined above:


  public class Main {

    public static void main(String args[]) {

      MyBean myBean = new MyBean();
      myBean.setName(null);

      System.out.println("Name is " + myBean.getName().length() +
        " characters long");

    }

  }

This piece of code will result in a NullPointerException, but while the root cause of the problem is that we set null into the bean property, the bean property should have rejected that as an invalid value. We need some inbound, and just as importantly, outbound validation on the Name bean property.

Inbound / Outbound Validation

It's easy to add value constraints to a bean property by adding a checks on the getter and setter which throw an appropriate unchecked exception when the pre-conditions are not met.

  public class MyBean {

    private String m_name = null;

    public String getName() {
      if(m_name == null) {
        throw new IllegalStateException("name not set");
      }
      return m_name;
    }

    public void setName(String name) {
      if(name == null) {
        throw new IllegalArgumentException("name cannot be null");
      }
      return m_name;
    }

    ...

  }

The three changes we've made to our bean property now are:

  • Explicitly initialized the field to an explicit empty value, in this case null. For other data types, a different value, or even a separate flag field might be used.
  • Added outbound validation to the getter. If the field is not set we will throw an InvalidStateException.
  • Added inbound validation to the setter. If an attempt is made to set the name to null we will throw an InvalidArgumentException

On the exception thrown by the setter: a while back I had a debate with a co-worker about whether IllegalArgumentException or NullPointerException was the right one to throw here. To me NullPointerException pretty clearly suggests you have tried to access a member of a pointer that is null, which is not what the inbound validation on our setter is doing. We have decided that null is not a valid value for this parameter, so to me IllegalArgumentException is a better semantic fit.

It's important to note that the getter requires validation just as much as the setter. This is a defensive-programming technique against some piece of code being inadvertently written to set the field directly, and also against us forgetting to set the bean property in the first place.

Initially, the code in our Main class (above) was going to result in a NullPointerException. Now it will result in an IllegalArgumentException. So have we really achieved anything? Well, the root cause of the problem - the null we are passing in - hasn't changed, and our bean property code can't do anything about that. But what our bean property code can and now does do, is accurately report the nature of the problem, which makes tracking down the issue a lot easier.

With our first bean property implementation, we would have gotten the following stack trace out of Main.main:

    java.lang.NullPointerException
        at perfectbeanproperty.Main.main(Main.java:66)
    Exception in thread "main"
    Java Result: 1

After adding validation to the bean property, we now get the following stack trace:

    java.lang.IllegalArgumentException: name cannot be null
        at perfectbeanproperty.MyBean.setName(MyBean.java:56)
        at perfectbeanproperty.Main.main(Main.java:64)
    Exception in thread "main"
    Java Result: 1

The second stack trace gives both a clear explanation of the problem, and a line number trace that takes us back to Main.java line 64, which was where the setter was called passing null.

Read Only Properties

If a property is read-only, we could just implement the getter, and let other code in the class write to the bean properties field directly. However, this would remove the inbound validation which is not just their to protect the bean property against externally provided invalid values, but also from invalid values provided from other code in the same class. So for read only properties we should retain the setter, but just make it private.

  public class MyBean {

    private String m_name = null;

    public String getName() {
      if(m_name == null) {
        throw new IllegalStateException("name not set");
      }
      return m_name;
    }

    private void setName(String name) {
      if(name == null) {
        throw new IllegalArgumentException("name cannot be null");
      }
      return m_name;
    }

    ...

  }

All of the other code in our class that wants to set the bean property MUST do so via the setter. This is an important self-defense mechanism - defending against ourselves making coding errors by checking the values we set even when it's from code completely under our control.

Write-Only Properties

It's a kooky concept, but Microsoft thought it was valid enough to add "writeonly" keywords to the .NET languages for use with properties.

I've never seen anyone actually use a write-only property, but if you needed one of course you would make the getter private and the setter public. As with the read-only case it is important to keep the getter with it's outbound validation as a defensive coding mechansim.

Mandatory Properties

Mandatory Properties Belong in Constructors

For things like Name, ID or other key information it may not make sense to allow an empty value. These mandatory bean properties should be set via each of your class' constructors.

For example, if the Name property was mandatory for the MyBean class above, we would add a constructor that accepts Name as a parameter, and would not provide a no-arg constructor:

  public class MyBean {

    public MyBean(String name) {
      this.setName(name);
    }

    private String m_name = null;

    public String getName() {
      if(m_name == null) {
        throw new IllegalStateException("name not set");
      }
      return m_name;
    }

    private void setName(String name) {
      if(name == null) {
        throw new IllegalArgumentException("name cannot be null");
      }
      return m_name;
    }

    ...

  }

Note that even in the constructor, we use the setter to set the bean property's underlying field because we always want that inbound validation to run.

Default Values for Mandatory Properties Also Belong in Constructors

Where a mandatory property has a default value, you could change the field declaration to initialize it to that default value. Because you haven't explicitly set the value of the property, it's possible that it might end up using the default value when you don't expect it to.

Retaining the model of initializing the field with an empty value and placing outbound validation on the getter means that when we get the properties value we can be certain that value was explicitly set by some other piece of code and validated. To add default values then we call the setter from our POJO's constructor.

Conveniently, when default values are derived from some computation, the constructor is a good place to do that.

  public class MyBean {

    private int m_nextId = 0;
    private static synchronized int nextId() {
      return ++m_nextId;
    }

    public MyBean() {
      this.setName("Untitled " + MyBean.nextId());
    }

    public MyBean(String name) {
      this.setName(name);
    }

    private String m_name = null;

    public String getName() {
      if(m_name == null) {
        throw new IllegalStateException("name not set");
      }
      return m_name;
    }

    private void setName(String name) {
      if(name == null) {
        throw new IllegalArgumentException("name cannot be null");
      }
      return m_name;
    }

    ...

  }

Optional Properties

Querying State

Optional properties may or may not have a value. When consuming optional properties, we need to be able to check whether or not the property has a value.

Our outbound validation on our getter means that we can't do something like this:


  if(myBean.getName() == null) {
    ...
  }

If the Name property is indeed null, the outbound validation will throw an IllegalStateException. It may be valid for the property to be empty, but that emptiness is not really a value, it's a state associated with the bean property. So what we need is a special method to check the properties state:


  public boolean hasName() {
    return !(m_name == null);
  }

And our state check becomes:


  if(!myBean.hasName()) {
    ...
  }

This allows us to write code that branches based upon the state of an optional bean property while still retaining strong outbound validation so that the getter will cough if the property doesn't have a valid value.

There's still a problem with this approach though in some application contexts, such as expression language (EL) expressions in a JSP page which rely exclusively on the bean property naming conventions. In those contexts hasName wouldn't be accessible, so we couldn't branch. If we change our state querying method's name to make it into a read-only bean property by calling it isNameSet, then we have a solution that works in those contexts as well:


  <c:choose>
    <c:when test="${myBean.nameSet}">
      <c:out value="${myBean.name}" />
    </c:when>
    <c:otherwise>
      Name is not set
    </c:otherwise>
  </c:choose>

Clearing Optional Properties

We may want to be able to reset the value of an optional property back to it's empty state. Here we face a similar problem to the one we had when trying to check if an optional property was set - in the following code, our in-bound validation will kick in and reject the valid value.


  myBean.setName(null);

Again, null represents the empty state, not a value. So we need a dedicated method to set the field to that state:


  public void clearName() {
    m_name = null;
  }

Other Examples

Introduction

As most data types in Java are Object derivatives, null is the logical empty value. But for primitive data types, we may need to put a bit more thought into what is the right empty value and validation for our property. Some example bean property implementations follow:

Primitive Numeric Data Types

Overview

Primitive longs, doubles, ints and so on can't be null in Java, so if we want to implement emptiness checking for a bean property of one of these types we have a couple of options:

  • For properties that have a finite valid range, we can choose some special value outside of that range to represent our empty state flag.
  • Add a private state flag field alongside the bean property field

Special Value

Where are property has a specific range of values that are valid, we can just choose one that is not valid to be the flag value representing the empty state. For example, if we had an int property that could only hold positive values then we could choose, say, -1 to be our empty state flag value.


  private int m_length = -1;

  public int getLength() {
    if(m_length == -1) {
      throw new IllegalStateException(
        "length not set");
    }
    return m_length;
  }

  public void setLength(int length) {
    if(length <= 0) {
      throw new IllegalArgumentException(
        "length must be greater than zero")
    }
    m_length = length;
  }

  public boolean isLengthSet() {
    return m_length != -1;
  }

  public void clearLength() {
    m_length = -1;
  }

Private State Flag

If a property can hold any value, then we need a separate field to flag it's empty state.

For example, the following bean property represents one of the three cartesian co-ordinates, and can take any value, positive, negative or zero.


  private double m_x = 0;
  private boolean m_xSet = false;

  public double getX() {
    if(!m_xSet) {
      throw new IllegalStateException(
        "x not set");
    }
    return m_x;
  }

  public void setX(double x) {
    m_x = x;
  }

  public boolean isXSet() {
    return m_xSet;
  }

  public void clearX() {
    m_x = 0;
    m_xSet = false;
  }

Booleans

Since booleans only have two possible states, it's unlikely you'd ever create a boolean bean property and then define one of the two possible values to be illegal. So to lock down a boolean field, we need to add a separate flag field, as we did with the numeric property above.


  private boolean m_synch = false;
  private boolean m_synch= false;

  public boolean isSynch() {
    if(m_synch) {
      throw new IllegalStateException(
        "synch not set");
    }
    return m_synch;
  }

  public void setSynch(boolean synch) {
    m_synch = synch;
    m_synchSet = true;
  }

  public boolean isSynchSet() {
    return m_synchSet;
  }

  public void clearSynch() {
    m_synch = false;
    m_synchSet = false;
  }

Related


Comments

Re: Back to Basics: The Perfect Java Bean Property

I totally agree with your reasoning. However, I get a java.lang.reflect.InvocationTargetException when Hibernate tries to call the getter method of my pojo. Is there any way around this?
Comment from Johan B on 10 March 2008 16:06:08 #

About

I'm a software professional who works mostly with Java and .NET technologies and who loves to ship. I've been writing software since I was around 6 or 7 when my parents bought a Commodore VIC-20 for the family.

I currently work as a SSDE with Microsoft's Healthcare Solutions Group in Bangkok, Thailand where we work closely with Bumrungrad International Hospital. I'm often also found working out of my home office in Melbourne, Australia.

Body of Work

Krypton - Industrial Age Software Builds

Krypton is a build tool that offers a build paradigm akin to an industrial assembly line.
website

Bamboo - Stress Free Page Layouts

Bamboo offers an alternative approach to implementing page layouts that eliminates the stress caused by CSS browser incompatibilities.
website / sourceforge

Other