Sunday 17 January 2016

IndexOutOfBoundsException when using commons BeanUtils in Java 8

When migrating from Java 7 to Java 8, we noticed a strange issue with apache commons BeanUtils.populate(target, properties) method, when collections were populated from properties. They started failing with the following exception


Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.get(ArrayList.java:429)
at org.apache.commons.beanutils.PropertyUtilsBean.getIndexedProperty(PropertyUtilsBean.java:513)
at org.apache.commons.beanutils.PropertyUtilsBean.getIndexedProperty(PropertyUtilsBean.java:410)
at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:768)
at org.apache.commons.beanutils.PropertyUtilsBean.getProperty(PropertyUtilsBean.java:846)
at org.apache.commons.beanutils.BeanUtilsBean.setProperty(BeanUtilsBean.java:903)
at org.apache.commons.beanutils.BeanUtilsBean.populate(BeanUtilsBean.java:830)
at org.apache.commons.beanutils.BeanUtils.populate(BeanUtils.java:433)
at sample.test.BeanPopulateTest.main(BeanPopulateTest.java:16)

To make things clear, I am going to use the example below.

Employee has a collection of Address objects. Note that Employee object has additional methods, to get the address at a given index and set address at an index. These methods are used by the BeanPopulator, to populate the values from OGNL expressions.

Employee.java

import java.util.ArrayList;
import java.util.List;

public class Employee {

  private List addresses = new ArrayList<Address>();

  public List<Address> getAddresses() {
    return addresses;
  }

  public void setAddresses(List<Address> addresses) {
    this.addresses = addresses;
  }

  public Address getAddresses(final int index) {
    if (index <= addresses.size()) {
       for (int i = addresses.size(); i <= index; i++) {
         addresses.add(new Address());
       }
    }
    return addresses.get(index);
  }

  public void setAddresses(final int index, final Address address) {
    this.addresses.add(index, address);
  }
}

Address.java

public class Address {
 private String postCode;

 public Address() {
 }

 public String getPostCode() {
  return postCode;
 }

 public void setPostCode(String postCode) {
  this.postCode = postCode;
 }
} 

A simple test to populate the Address collection revealed what was happening

import java.util.HashMap;
import java.util.Map;
import org.apache.commons.beanutils.BeanUtils;

public class BeanPopulateTest {
   public static void main(String[] args) throws Exception {
 
        Employee employee = new Employee();
 
        Map<String,String> properties = new HashMap<String,String>();
 properties.put("addresses[0].postCode", "TES456");

 BeanUtils.populate(employee, properties);

 System.out.println(employee.getAddresses().get(0).getPostCode());
   }
}

After a few hours of investigation and looking at the BeanUtils code, I realized that Java 8 property descriptor for Employee.addresses (collections) field is different from Java 7.

BeanInfo info = Introspector.getBeanInfo(Employee.class);
PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
for (int i = 0; i < descriptors.length; i++) {
  System.out.println(descriptors[i].getName() + " " + descriptors[i].getClass().getName());
}

The output from the above code is

Java 8
java.beans.PropertyDescriptor:addresses

Java 7
java.beans.IndexedPropertyDescriptor:addresses

This causes BeanUtils to process the address collection as a non-indexed property and invoke the get(index) method on the collection, causing the IndexOutOfBoundsException.

A simple workaround for the issue is to change the names of the default getter/setter methods for the addresses collection, from getAddresses/setAddresses to getAddressList/setAddressList. All references to the existing method calls will have to be replaced with the new method names.

Employee.java (fix)

import java.util.ArrayList;
import java.util.List;

public class Employee {

 private List addresses = new ArrayList<Address>();

 public List<Address> getAddressList() {
  return addresses;
 }

 public void setAddressList(List<Address> addresses) {
  this.addresses = addresses;
 }

 public Address getAddresses(final int index) {
  if (index <= addresses.size()) {
   for (int i = addresses.size(); i <= index; i++) {
    addresses.add(new Address());
   }
  }
  return addresses.get(index);
 }

 public void setAddresses(final int index, final Address address) {
  this.addresses.add(index, address);
 }
}




11 comments:

  1. If you populate more than 1 element into the list it still throw IndexOutOfBoundsException.

    The fixed method:

    public Address getAddresses(final int index) {
    if (index >= addresses.size()) {
    for (int i = addresses.size(); i <= index; i++) {
    addresses.add(new Address());
    }
    }
    return addresses.get(index);
    }

    look for line: if (index >= addresses.size()) {

    ReplyDelete
  2. Thanks for the post, I am techno savvy. I believe you hit the nail right on the head. I am highly impressed with your blog. It is very nicely explained. Your article adds best knowledge to our Java EE Training in Chennai. or learn thru Java EE Training in Chennai Students.

    ReplyDelete
  3. Some us know all relating to the compelling medium you present powerful steps on this blog and therefore strongly encourage
    contribution from other ones on this subject while our own child is truly discovering a great deal.
    Have fun with the remaining portion of the year.
    Selenium training in bangalore
    Selenium training in Chennai
    Selenium training in Bangalore
    Selenium training in Pune
    Selenium Online training

    ReplyDelete
  4. Hello! Thank you.. I spent two three days with this issue and happy that your fixed worked. This application using struts 1.1 in Liberty PCF.

    ReplyDelete
  5. after couple of day investing the issue finally your blog made my day

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
  7. The solution has worked also for me.

    After three days of unsuccessfull analysis I has been lucky to have found this article.
    Thank you very much for share your experience.
    The same problem, for the application I work, has shown up after a migration of the server environment. We switched an old project relilzed mostly in Java 1.4 from building in Java 1.6 compatibility to Java 1.8.

    ReplyDelete
  8. I have tried this solution in stand-alone test project and got the same results which gave me hope that this was my solution too. Not!
    I'm using Struts 1 where a property obviously can have only one name.
    Struts uses the standard setter/getter naming convention (i.e. if property schoolAddresses, then the setter/getter names default to setSchoolAddresses and getSchoolAddresses. So you see that I don't have the luxury of renaming the setters/getters on the server side because Struts will not find them.
    Any and all ideas are welcome.

    ReplyDelete