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
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
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);
}
}
If you populate more than 1 element into the list it still throw IndexOutOfBoundsException.
ReplyDeleteThe 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()) {
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.
ReplyDeleteSome us know all relating to the compelling medium you present powerful steps on this blog and therefore strongly encourage
ReplyDeletecontribution 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
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.
ReplyDeleteNice Post...Thanks for sharing nice information...
ReplyDeletepega training in bangalore
This article is very nice content. thanking you.
ReplyDeletePython Training in Chennai
Python Training in Training
Python Training in Bangalore
Python Hyderabad
Python Training in Coimbatore
after couple of day investing the issue finally your blog made my day
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteThe solution has worked also for me.
ReplyDeleteAfter 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.
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!
ReplyDeleteI'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.
Mmorpg Oyunlar
ReplyDeleteinstagram takipçi satın al
tiktok jeton hilesi
tiktok jeton hilesi
Saç ekimi antalya
referans kimliği nedir
İnstagram takipçi satın al
metin2 pvp serverlar
Instagram Takipci Satin Al
Thank you for this detailed explanation and solution! Your insights on Java 8's handling of property descriptors will surely save others time and headaches. Great job!
ReplyDeletecyber security internship for freshers | cyber security internship in chennai | ethical hacking internship | cloud computing internship | aws internship | ccna course in chennai | java internship online