You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2023/02/18 13:53:22 UTC

[commons-beanutils] 04/05: Sort members

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-beanutils.git

commit 26a7831ea71b282646a229dde2401fe12c509f2a
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Wed Feb 15 14:32:55 2023 -0500

    Sort members
---
 .../beanutils2/BaseDynaBeanMapDecorator.java       |  176 +-
 .../apache/commons/beanutils2/BasicDynaBean.java   |  202 +--
 .../apache/commons/beanutils2/BasicDynaClass.java  |  132 +-
 .../apache/commons/beanutils2/BeanComparator.java  |  144 +-
 .../commons/beanutils2/BeanIntrospectionData.java  |   58 +-
 .../org/apache/commons/beanutils2/BeanMap.java     |  706 ++++----
 .../apache/commons/beanutils2/BeanPredicate.java   |   72 +-
 .../BeanPropertyValueEqualsPredicate.java          |  102 +-
 .../org/apache/commons/beanutils2/BeanUtils.java   |   62 +-
 .../apache/commons/beanutils2/BeanUtilsBean.java   |  252 +--
 .../commons/beanutils2/ConstructorUtils.java       |   18 +-
 .../beanutils2/ContextClassLoaderLocal.java        |   34 +-
 .../commons/beanutils2/ConversionException.java    |    4 +-
 .../apache/commons/beanutils2/ConvertUtils.java    |   76 +-
 .../commons/beanutils2/ConvertUtilsBean.java       |  572 +++----
 .../beanutils2/DefaultBeanIntrospector.java        |   62 +-
 .../beanutils2/DefaultIntrospectionContext.java    |   38 +-
 .../org/apache/commons/beanutils2/DynaBean.java    |   22 +-
 .../beanutils2/DynaBeanPropertyMapDecorator.java   |   18 +-
 .../org/apache/commons/beanutils2/DynaClass.java   |   30 +-
 .../apache/commons/beanutils2/DynaProperty.java    |  222 +--
 .../beanutils2/FluentPropertyBeanIntrospector.java |   34 +-
 .../commons/beanutils2/IntrospectionContext.java   |   40 +-
 .../apache/commons/beanutils2/LazyDynaBean.java    |  966 +++++------
 .../apache/commons/beanutils2/LazyDynaClass.java   |  238 +--
 .../apache/commons/beanutils2/LazyDynaList.java    |  306 ++--
 .../org/apache/commons/beanutils2/LazyDynaMap.java |  394 ++---
 .../beanutils2/MappedPropertyDescriptor.java       |  506 +++---
 .../org/apache/commons/beanutils2/MethodUtils.java | 1800 ++++++++++----------
 .../apache/commons/beanutils2/PropertyUtils.java   |   88 +-
 .../commons/beanutils2/PropertyUtilsBean.java      |  690 ++++----
 .../apache/commons/beanutils2/WeakFastHashMap.java | 1012 +++++------
 .../apache/commons/beanutils2/WrapDynaBean.java    |  156 +-
 .../apache/commons/beanutils2/WrapDynaClass.java   |  312 ++--
 .../beanutils2/converters/ArrayConverter.java      |  174 +-
 .../beanutils2/converters/BooleanConverter.java    |   68 +-
 .../beanutils2/converters/CharacterConverter.java  |   26 +-
 .../beanutils2/converters/ClassConverter.java      |   24 +-
 .../beanutils2/converters/DateTimeConverter.java   |  446 ++---
 .../beanutils2/converters/DurationConverter.java   |   22 +-
 .../beanutils2/converters/EnumConverter.java       |   24 +-
 .../beanutils2/converters/FileConverter.java       |   22 +-
 .../beanutils2/converters/LocalTimeConverter.java  |   22 +-
 .../beanutils2/converters/MonthDayConverter.java   |   22 +-
 .../beanutils2/converters/NumberConverter.java     |  410 ++---
 .../beanutils2/converters/OffsetTimeConverter.java |   22 +-
 .../beanutils2/converters/PathConverter.java       |   22 +-
 .../beanutils2/converters/PeriodConverter.java     |   22 +-
 .../beanutils2/converters/ShortConverter.java      |    8 +-
 .../beanutils2/converters/StringConverter.java     |   22 +-
 .../beanutils2/converters/URIConverter.java        |   22 +-
 .../beanutils2/converters/URLConverter.java        |   22 +-
 .../beanutils2/converters/UUIDConverter.java       |   22 +-
 .../beanutils2/converters/YearConverter.java       |   22 +-
 .../beanutils2/converters/YearMonthConverter.java  |   22 +-
 .../beanutils2/converters/ZoneIdConverter.java     |   22 +-
 .../beanutils2/converters/ZoneOffsetConverter.java |   22 +-
 .../commons/beanutils2/locale/LocaleBeanUtils.java |  330 ++--
 .../beanutils2/locale/LocaleBeanUtilsBean.java     |  684 ++++----
 .../beanutils2/locale/LocaleConvertUtils.java      |  236 +--
 .../beanutils2/locale/LocaleConvertUtilsBean.java  |    6 +-
 .../apache/commons/beanutils2/AbstractChild.java   |    8 +-
 .../apache/commons/beanutils2/AbstractParent.java  |   20 +-
 .../org/apache/commons/beanutils2/AlphaBean.java   |   14 +-
 .../commons/beanutils2/BasicDynaBeanTestCase.java  |  154 +-
 .../commons/beanutils2/BeanComparatorTestCase.java |  104 +-
 .../beanutils2/BeanIntrospectionDataTestCase.java  |   42 +-
 .../apache/commons/beanutils2/BeanMapTestCase.java |  330 ++--
 .../commons/beanutils2/BeanPredicateTestCase.java  |   12 +-
 .../BeanPropertyValueChangeConsumerTestCase.java   |  234 +--
 .../BeanPropertyValueEqualsPredicateTestCase.java  |  214 +--
 .../BeanToPropertyValueTransformerTestCase.java    |  222 +--
 .../commons/beanutils2/BeanUtilsBean2TestCase.java |   14 +-
 .../commons/beanutils2/BeanUtilsBeanTestCase.java  | 1624 +++++++++---------
 .../commons/beanutils2/BeanUtilsBenchCase.java     |   24 +-
 .../commons/beanutils2/BeanWithInnerBean.java      |   18 +-
 .../commons/beanutils2/BeanificationTestCase.java  |  548 +++---
 .../org/apache/commons/beanutils2/BenchBean.java   |  112 +-
 .../org/apache/commons/beanutils2/BetaBean.java    |   12 +-
 .../beanutils2/ConstructorUtilsTestCase.java       |  134 +-
 .../commons/beanutils2/ConvertUtilsTestCase.java   |  180 +-
 .../beanutils2/DynaBeanMapDecoratorTestCase.java   |  150 +-
 .../commons/beanutils2/DynaBeanUtilsTestCase.java  |  768 ++++-----
 .../commons/beanutils2/DynaPropertyTestCase.java   |   46 +-
 .../beanutils2/DynaPropertyUtilsTestCase.java      |   60 +-
 .../beanutils2/FluentIntrospectionTestBean.java    |   18 +-
 .../FluentPropertyBeanIntrospectorTestCase.java    |   24 +-
 .../beanutils2/IndexedPropertyTestCase.java        |  210 +--
 .../apache/commons/beanutils2/IndexedTestBean.java |   66 +-
 .../commons/beanutils2/LazyDynaBeanTestCase.java   |  488 +++---
 .../commons/beanutils2/LazyDynaClassTestCase.java  |   46 +-
 .../commons/beanutils2/LazyDynaListTestCase.java   |  378 ++--
 .../commons/beanutils2/LazyDynaMapTestCase.java    |  456 ++---
 .../commons/beanutils2/MappedPropertyTestBean.java |   60 +-
 .../commons/beanutils2/MappedPropertyTestCase.java |  170 +-
 .../commons/beanutils2/MethodUtilsTestCase.java    |  312 ++--
 .../apache/commons/beanutils2/NestedTestBean.java  |   52 +-
 .../apache/commons/beanutils2/PrimitiveBean.java   |   36 +-
 .../commons/beanutils2/PropertyUtilsBenchCase.java |   28 +-
 .../commons/beanutils2/PropertyUtilsTestCase.java  | 1430 ++++++++--------
 ...SuppressPropertiesBeanIntrospectorTestCase.java |  136 +-
 .../org/apache/commons/beanutils2/TestBean.java    |  694 ++++----
 .../commons/beanutils2/WrapDynaBeanTestCase.java   |  216 +--
 .../commons/beanutils2/bugs/Jira157TestCase.java   |  114 +-
 .../commons/beanutils2/bugs/Jira18TestCase.java    |   64 +-
 .../commons/beanutils2/bugs/Jira273TestCase.java   |   86 +-
 .../commons/beanutils2/bugs/Jira298TestCase.java   |   46 +-
 .../commons/beanutils2/bugs/Jira339TestCase.java   |   82 +-
 .../commons/beanutils2/bugs/Jira345TestCase.java   |   62 +-
 .../commons/beanutils2/bugs/Jira349TestCase.java   |   72 +-
 .../commons/beanutils2/bugs/Jira357TestCase.java   |  202 +--
 .../commons/beanutils2/bugs/Jira358TestCase.java   |   18 +-
 .../commons/beanutils2/bugs/Jira359TestCase.java   |   72 +-
 .../commons/beanutils2/bugs/Jira368TestCase.java   |   18 +-
 .../commons/beanutils2/bugs/Jira369TestCase.java   |   58 +-
 .../commons/beanutils2/bugs/Jira381TestCase.java   |   44 +-
 .../commons/beanutils2/bugs/Jira411TestCase.java   |   26 +-
 .../commons/beanutils2/bugs/Jira422TestCase.java   |   12 +-
 .../commons/beanutils2/bugs/Jira422bTestCase.java  |   12 +-
 .../commons/beanutils2/bugs/Jira454TestCase.java   |   14 +-
 .../commons/beanutils2/bugs/Jira456TestCase.java   |   30 +-
 .../commons/beanutils2/bugs/Jira458TestCase.java   |   12 +-
 .../commons/beanutils2/bugs/Jira465TestCase.java   |  108 +-
 .../commons/beanutils2/bugs/Jira492TestCase.java   |   74 +-
 .../commons/beanutils2/bugs/Jira520TestCase.java   |   22 +-
 .../commons/beanutils2/bugs/Jira61TestCase.java    |  186 +-
 .../commons/beanutils2/bugs/Jira87TestCase.java    |   18 +-
 .../commons/beanutils2/bugs/Jira92TestCase.java    |   14 +-
 .../beanutils2/bugs/other/Jira18BeanFactory.java   |   64 +-
 .../beanutils2/bugs/other/Jira273BeanFactory.java  |  102 +-
 .../beanutils2/bugs/other/Jira298BeanFactory.java  |   30 +-
 .../bugs/other/Jira492IndexedListsSupport.java     |    8 +-
 .../beanutils2/bugs/other/Jira61BeanFactory.java   |   64 +-
 .../beanutils2/bugs/other/Jira87BeanFactory.java   |   42 +-
 .../converters/BaseLocaleConverterTestCase.java    |  176 +-
 .../BigDecimalLocaleConverterTestCase.java         |  102 +-
 .../BigIntegerLocaleConverterTestCase.java         |  100 +-
 .../converters/ByteLocaleConverterTestCase.java    |   96 +-
 .../converters/DateLocaleConverterTestCase.java    |  316 ++--
 .../converters/DoubleLocaleConverterTestCase.java  |  102 +-
 .../converters/EnumConverterTestCase.java          |    8 +-
 .../converters/FloatLocaleConverterTestCase.java   |  102 +-
 .../converters/IntegerLocaleConverterTestCase.java |   98 +-
 .../converters/LongLocaleConverterTestCase.java    |  100 +-
 .../converters/ShortLocaleConverterTestCase.java   |  100 +-
 .../expression/DefaultResolverTestCase.java        |   78 +-
 .../beanutils2/locale/LocaleBeanUtilsTestCase.java |   42 +-
 .../locale/LocaleBeanificationTestCase.java        |  544 +++---
 .../locale/LocaleConvertUtilsTestCase.java         |  474 +++---
 .../memoryleaktests/MemoryLeakTestCase.java        |  342 ++--
 .../memoryleaktests/pojotests/SomePojo.java        |    8 +-
 .../commons/beanutils2/priv/PackageBean.java       |    8 +-
 .../commons/beanutils2/priv/PrivateBean.java       |   46 +-
 .../commons/beanutils2/priv/PublicSubBean.java     |   10 +-
 .../beanutils2/sql/DynaResultSetTestCase.java      |   62 +-
 .../commons/beanutils2/sql/DynaRowSetTestCase.java |  282 +--
 .../commons/beanutils2/sql/TestResultSet.java      |  292 ++--
 .../beanutils2/sql/TestResultSetMetaData.java      |   92 +-
 158 files changed, 13765 insertions(+), 13765 deletions(-)

diff --git a/src/main/java/org/apache/commons/beanutils2/BaseDynaBeanMapDecorator.java b/src/main/java/org/apache/commons/beanutils2/BaseDynaBeanMapDecorator.java
index 28794e50..bc3747d9 100644
--- a/src/main/java/org/apache/commons/beanutils2/BaseDynaBeanMapDecorator.java
+++ b/src/main/java/org/apache/commons/beanutils2/BaseDynaBeanMapDecorator.java
@@ -48,8 +48,51 @@ import java.util.Set;
  */
 public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
 
+    /**
+     * Map.Entry implementation.
+     */
+    private static class MapEntry<K> implements Map.Entry<K, Object> {
+
+        private final K key;
+        private final Object value;
+
+        MapEntry(final K key, final Object value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (!(o instanceof Map.Entry)) {
+                return false;
+            }
+            final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
+            return key.equals(e.getKey()) && (value == null ? e.getValue() == null : value.equals(e.getValue()));
+        }
+
+        @Override
+        public K getKey() {
+            return key;
+        }
+
+        @Override
+        public Object getValue() {
+            return value;
+        }
+
+        @Override
+        public int hashCode() {
+            return key.hashCode() + (value == null ? 0 : value.hashCode());
+        }
+
+        @Override
+        public Object setValue(final Object value) {
+            throw new UnsupportedOperationException();
+        }
+    }
     private final DynaBean dynaBean;
     private final boolean readOnly;
+
     private transient Set<K> keySet;
 
     /**
@@ -77,15 +120,6 @@ public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
         this.readOnly = readOnly;
     }
 
-    /**
-     * Indicate whether the Map is read only.
-     *
-     * @return {@code true} if the Map is read only, otherwise {@code false}.
-     */
-    public boolean isReadOnly() {
-        return readOnly;
-    }
-
     /**
      * clear() operation is not supported.
      *
@@ -132,6 +166,14 @@ public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
         return false;
     }
 
+    /**
+     * Converts the name of a property to the key type of this decorator.
+     *
+     * @param propertyName the name of a property
+     * @return the converted key to be used in the decorated map
+     */
+    protected abstract K convertKey(String propertyName);
+
     /**
      * <p>
      * Returns the Set of the property/value mappings in the decorated {@link DynaBean}.
@@ -166,6 +208,24 @@ public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
         return getDynaBean().get(toString(key));
     }
 
+    /**
+     * Provide access to the underlying {@link DynaBean} this Map decorates.
+     *
+     * @return the decorated {@link DynaBean}.
+     */
+    public DynaBean getDynaBean() {
+        return dynaBean;
+    }
+
+    /**
+     * Convenience method to retrieve the {@link DynaProperty}s for this {@link DynaClass}.
+     *
+     * @return The an array of the {@link DynaProperty}s.
+     */
+    private DynaProperty[] getDynaProperties() {
+        return getDynaBean().getDynaClass().getDynaProperties();
+    }
+
     /**
      * Indicate whether the decorated {@link DynaBean} has any properties.
      *
@@ -176,6 +236,15 @@ public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
         return getDynaProperties().length == 0;
     }
 
+    /**
+     * Indicate whether the Map is read only.
+     *
+     * @return {@code true} if the Map is read only, otherwise {@code false}.
+     */
+    public boolean isReadOnly() {
+        return readOnly;
+    }
+
     /**
      * <p>
      * Returns the Set of the property names in the decorated {@link DynaBean}.
@@ -267,6 +336,16 @@ public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
         return getDynaProperties().length;
     }
 
+    /**
+     * Convenience method to convert an Object to a String.
+     *
+     * @param obj The Object to convert
+     * @return String representation of the object
+     */
+    private String toString(final Object obj) {
+        return obj == null ? null : obj.toString();
+    }
+
     /**
      * Returns the set of property values in the decorated {@link DynaBean}.
      *
@@ -284,83 +363,4 @@ public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
         return Collections.unmodifiableList(values);
     }
 
-    /**
-     * Provide access to the underlying {@link DynaBean} this Map decorates.
-     *
-     * @return the decorated {@link DynaBean}.
-     */
-    public DynaBean getDynaBean() {
-        return dynaBean;
-    }
-
-    /**
-     * Converts the name of a property to the key type of this decorator.
-     *
-     * @param propertyName the name of a property
-     * @return the converted key to be used in the decorated map
-     */
-    protected abstract K convertKey(String propertyName);
-
-    /**
-     * Convenience method to retrieve the {@link DynaProperty}s for this {@link DynaClass}.
-     *
-     * @return The an array of the {@link DynaProperty}s.
-     */
-    private DynaProperty[] getDynaProperties() {
-        return getDynaBean().getDynaClass().getDynaProperties();
-    }
-
-    /**
-     * Convenience method to convert an Object to a String.
-     *
-     * @param obj The Object to convert
-     * @return String representation of the object
-     */
-    private String toString(final Object obj) {
-        return obj == null ? null : obj.toString();
-    }
-
-    /**
-     * Map.Entry implementation.
-     */
-    private static class MapEntry<K> implements Map.Entry<K, Object> {
-
-        private final K key;
-        private final Object value;
-
-        MapEntry(final K key, final Object value) {
-            this.key = key;
-            this.value = value;
-        }
-
-        @Override
-        public boolean equals(final Object o) {
-            if (!(o instanceof Map.Entry)) {
-                return false;
-            }
-            final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
-            return key.equals(e.getKey()) && (value == null ? e.getValue() == null : value.equals(e.getValue()));
-        }
-
-        @Override
-        public int hashCode() {
-            return key.hashCode() + (value == null ? 0 : value.hashCode());
-        }
-
-        @Override
-        public K getKey() {
-            return key;
-        }
-
-        @Override
-        public Object getValue() {
-            return value;
-        }
-
-        @Override
-        public Object setValue(final Object value) {
-            throw new UnsupportedOperationException();
-        }
-    }
-
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/BasicDynaBean.java b/src/main/java/org/apache/commons/beanutils2/BasicDynaBean.java
index f2515b5c..8f90c732 100644
--- a/src/main/java/org/apache/commons/beanutils2/BasicDynaBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/BasicDynaBean.java
@@ -55,18 +55,6 @@ public class BasicDynaBean implements DynaBean, Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    /**
-     * Constructs a new {@code DynaBean} associated with the specified
-     * {@code DynaClass} instance.
-     *
-     * @param dynaClass The DynaClass we are associated with
-     */
-    public BasicDynaBean(final DynaClass dynaClass) {
-
-        this.dynaClass = dynaClass;
-
-    }
-
     /**
      * The {@code DynaClass} "base class" that this DynaBean
      * is associated with.
@@ -82,23 +70,14 @@ public class BasicDynaBean implements DynaBean, Serializable {
     private transient Map<String, Object> mapDecorator;
 
     /**
-     * <p>
-     * Gets a Map representation of this DynaBean.
-     * <p>
-     * This, for example, could be used in JSTL in the following way to access
-     * a DynaBean's {@code fooProperty}:
-     * <ul><li>{@code ${myDynaBean.<b>map</b>.fooProperty}}</li></ul>
+     * Constructs a new {@code DynaBean} associated with the specified
+     * {@code DynaClass} instance.
      *
-     * @return a Map representation of this DynaBean
-     * @since 1.8.0
+     * @param dynaClass The DynaClass we are associated with
      */
-    public Map<String, Object> getMap() {
+    public BasicDynaBean(final DynaClass dynaClass) {
 
-        // cache the Map
-        if (mapDecorator == null) {
-            mapDecorator = new DynaBeanPropertyMapDecorator(this);
-        }
-        return mapDecorator;
+        this.dynaClass = dynaClass;
 
     }
 
@@ -251,6 +230,72 @@ public class BasicDynaBean implements DynaBean, Serializable {
 
     }
 
+    /**
+     * Gets the property descriptor for the specified property name.
+     *
+     * @param name Name of the property for which to retrieve the descriptor
+     * @return The property descriptor
+     *
+     * @throws IllegalArgumentException if this is not a valid property
+     *  name for our DynaClass
+     */
+    protected DynaProperty getDynaProperty(final String name) {
+
+        final DynaProperty descriptor = getDynaClass().getDynaProperty(name);
+        if (descriptor == null) {
+            throw new IllegalArgumentException
+                    ("Invalid property name '" + name + "'");
+        }
+        return descriptor;
+
+    }
+
+    /**
+     * <p>
+     * Gets a Map representation of this DynaBean.
+     * <p>
+     * This, for example, could be used in JSTL in the following way to access
+     * a DynaBean's {@code fooProperty}:
+     * <ul><li>{@code ${myDynaBean.<b>map</b>.fooProperty}}</li></ul>
+     *
+     * @return a Map representation of this DynaBean
+     * @since 1.8.0
+     */
+    public Map<String, Object> getMap() {
+
+        // cache the Map
+        if (mapDecorator == null) {
+            mapDecorator = new DynaBeanPropertyMapDecorator(this);
+        }
+        return mapDecorator;
+
+    }
+
+    /**
+     * Is an object of the source class assignable to the destination class?
+     *
+     * @param dest Destination class
+     * @param source Source class
+     * @return {@code true} if the source class is assignable to the
+     * destination class, otherwise {@code false}
+     */
+    protected boolean isAssignable(final Class<?> dest, final Class<?> source) {
+
+        if (dest.isAssignableFrom(source) ||
+                dest == Boolean.TYPE && source == Boolean.class ||
+                dest == Byte.TYPE && source == Byte.class ||
+                dest == Character.TYPE && source == Character.class ||
+                dest == Double.TYPE && source == Double.class ||
+                dest == Float.TYPE && source == Float.class ||
+                dest == Integer.TYPE && source == Integer.class ||
+                dest == Long.TYPE && source == Long.class ||
+                dest == Short.TYPE && source == Short.class) {
+            return true;
+        }
+        return false;
+
+    }
+
     /**
      * Remove any existing value for the specified key on the
      * specified mapped property.
@@ -275,36 +320,6 @@ public class BasicDynaBean implements DynaBean, Serializable {
 
     }
 
-    /**
-     * Sets the value of a simple property with the specified name.
-     *
-     * @param name Name of the property whose value is to be set
-     * @param value Value to which this property is to be set
-     *
-     * @throws ConversionException if the specified value cannot be
-     *  converted to the type required for this property
-     * @throws IllegalArgumentException if there is no property
-     *  of the specified name
-     * @throws NullPointerException if an attempt is made to set a
-     *  primitive property to null
-     */
-    @Override
-    public void set(final String name, final Object value) {
-
-        final DynaProperty descriptor = getDynaProperty(name);
-        if (value == null) {
-            if (descriptor.getType().isPrimitive()) {
-                throw new NullPointerException
-                        ("Primitive value for '" + name + "'");
-            }
-        } else if (!isAssignable(descriptor.getType(), value.getClass())) {
-            throw ConversionException.format("Cannot assign value of type '%s' to property '%s' of type '%s'", value.getClass().getName(), name,
-                    descriptor.getType().getName());
-        }
-        values.put(name, value);
-
-    }
-
     /**
      * Sets the value of an indexed property with the specified name.
      *
@@ -346,6 +361,36 @@ public class BasicDynaBean implements DynaBean, Serializable {
 
     }
 
+    /**
+     * Sets the value of a simple property with the specified name.
+     *
+     * @param name Name of the property whose value is to be set
+     * @param value Value to which this property is to be set
+     *
+     * @throws ConversionException if the specified value cannot be
+     *  converted to the type required for this property
+     * @throws IllegalArgumentException if there is no property
+     *  of the specified name
+     * @throws NullPointerException if an attempt is made to set a
+     *  primitive property to null
+     */
+    @Override
+    public void set(final String name, final Object value) {
+
+        final DynaProperty descriptor = getDynaProperty(name);
+        if (value == null) {
+            if (descriptor.getType().isPrimitive()) {
+                throw new NullPointerException
+                        ("Primitive value for '" + name + "'");
+            }
+        } else if (!isAssignable(descriptor.getType(), value.getClass())) {
+            throw ConversionException.format("Cannot assign value of type '%s' to property '%s' of type '%s'", value.getClass().getName(), name,
+                    descriptor.getType().getName());
+        }
+        values.put(name, value);
+
+    }
+
     /**
      * Sets the value of a mapped property with the specified name.
      *
@@ -378,49 +423,4 @@ public class BasicDynaBean implements DynaBean, Serializable {
 
     }
 
-    /**
-     * Gets the property descriptor for the specified property name.
-     *
-     * @param name Name of the property for which to retrieve the descriptor
-     * @return The property descriptor
-     *
-     * @throws IllegalArgumentException if this is not a valid property
-     *  name for our DynaClass
-     */
-    protected DynaProperty getDynaProperty(final String name) {
-
-        final DynaProperty descriptor = getDynaClass().getDynaProperty(name);
-        if (descriptor == null) {
-            throw new IllegalArgumentException
-                    ("Invalid property name '" + name + "'");
-        }
-        return descriptor;
-
-    }
-
-    /**
-     * Is an object of the source class assignable to the destination class?
-     *
-     * @param dest Destination class
-     * @param source Source class
-     * @return {@code true} if the source class is assignable to the
-     * destination class, otherwise {@code false}
-     */
-    protected boolean isAssignable(final Class<?> dest, final Class<?> source) {
-
-        if (dest.isAssignableFrom(source) ||
-                dest == Boolean.TYPE && source == Boolean.class ||
-                dest == Byte.TYPE && source == Byte.class ||
-                dest == Character.TYPE && source == Character.class ||
-                dest == Double.TYPE && source == Double.class ||
-                dest == Float.TYPE && source == Float.class ||
-                dest == Integer.TYPE && source == Integer.class ||
-                dest == Long.TYPE && source == Long.class ||
-                dest == Short.TYPE && source == Short.class) {
-            return true;
-        }
-        return false;
-
-    }
-
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/BasicDynaClass.java b/src/main/java/org/apache/commons/beanutils2/BasicDynaClass.java
index 06486213..9be1c3c4 100644
--- a/src/main/java/org/apache/commons/beanutils2/BasicDynaClass.java
+++ b/src/main/java/org/apache/commons/beanutils2/BasicDynaClass.java
@@ -35,6 +35,47 @@ public class BasicDynaClass implements DynaClass, Serializable {
 
     private static final long serialVersionUID = 1L;
 
+    /**
+     * The method signature of the constructor we will use to create
+     * new DynaBean instances.
+     */
+    private static final Class<?>[] CONSTRUCTOR_TYPES = { DynaClass.class };
+
+    /**
+     * The constructor of the {@code dynaBeanClass} that we will use
+     * for creating new instances.
+     */
+    protected transient Constructor<?> constructor;
+
+    /**
+     * The argument values to be passed to the constructor we will use
+     * to create new DynaBean instances.
+     */
+    protected Object[] constructorValues = { this };
+
+    /**
+     * The {@code DynaBean} implementation class we will use for
+     * creating new instances.
+     */
+    protected Class<?> dynaBeanClass = BasicDynaBean.class;
+
+    /**
+     * The "name" of this DynaBean class.
+     */
+    protected String name = this.getClass().getName();
+
+    /**
+     * The set of dynamic properties that are part of this DynaClass.
+     */
+    protected DynaProperty[] properties = DynaProperty.EMPTY_ARRAY;
+
+    /**
+     * The set of dynamic properties that are part of this DynaClass,
+     * keyed by the property name.  Individual descriptor instances will
+     * be the same instances as those in the {@code properties} list.
+     */
+    protected HashMap<String, DynaProperty> propertiesMap = new HashMap<>();
+
     /**
      * Constructs a new BasicDynaClass with default parameters.
      */
@@ -74,57 +115,30 @@ public class BasicDynaClass implements DynaClass, Serializable {
     }
 
     /**
-     * The constructor of the {@code dynaBeanClass} that we will use
-     * for creating new instances.
-     */
-    protected transient Constructor<?> constructor;
-
-    /**
-     * The method signature of the constructor we will use to create
-     * new DynaBean instances.
-     */
-    private static final Class<?>[] CONSTRUCTOR_TYPES = { DynaClass.class };
-
-    /**
-     * The argument values to be passed to the constructor we will use
-     * to create new DynaBean instances.
-     */
-    protected Object[] constructorValues = { this };
-
-    /**
-     * The {@code DynaBean} implementation class we will use for
-     * creating new instances.
-     */
-    protected Class<?> dynaBeanClass = BasicDynaBean.class;
-
-    /**
-     * The "name" of this DynaBean class.
-     */
-    protected String name = this.getClass().getName();
-
-    /**
-     * The set of dynamic properties that are part of this DynaClass.
-     */
-    protected DynaProperty[] properties = DynaProperty.EMPTY_ARRAY;
-
-    /**
-     * The set of dynamic properties that are part of this DynaClass,
-     * keyed by the property name.  Individual descriptor instances will
-     * be the same instances as those in the {@code properties} list.
+     * Gets the Class object we will use to create new instances in the
+     * {@code newInstance()} method.  This Class <strong>MUST</strong>
+     * implement the {@code DynaBean} interface.
+     *
+     * @return The class of the {@link DynaBean}
      */
-    protected HashMap<String, DynaProperty> propertiesMap = new HashMap<>();
+    public Class<?> getDynaBeanClass() {
+        return this.dynaBeanClass;
+    }
 
     /**
-     * Gets the name of this DynaClass (analogous to the
-     * {@code getName()} method of {@code java.lang.Class}, which
-     * allows the same {@code DynaClass} implementation class to support
-     * different dynamic classes, with different sets of properties.
+     * <p>Return an array of {@code PropertyDescriptor} for the properties
+     * currently defined in this DynaClass.  If no properties are defined, a
+     * zero-length array will be returned.</p>
      *
-     * @return the name of the DynaClass
+     * <p><strong>FIXME</strong> - Should we really be implementing
+     * {@code getBeanInfo()} instead, which returns property descriptors
+     * and a bunch of other stuff?</p>
+     *
+     * @return the set of properties for this DynaClass
      */
     @Override
-    public String getName() {
-        return this.name;
+    public DynaProperty[] getDynaProperties() {
+        return properties.clone();
     }
 
     /**
@@ -147,19 +161,16 @@ public class BasicDynaClass implements DynaClass, Serializable {
     }
 
     /**
-     * <p>Return an array of {@code PropertyDescriptor} for the properties
-     * currently defined in this DynaClass.  If no properties are defined, a
-     * zero-length array will be returned.</p>
-     *
-     * <p><strong>FIXME</strong> - Should we really be implementing
-     * {@code getBeanInfo()} instead, which returns property descriptors
-     * and a bunch of other stuff?</p>
+     * Gets the name of this DynaClass (analogous to the
+     * {@code getName()} method of {@code java.lang.Class}, which
+     * allows the same {@code DynaClass} implementation class to support
+     * different dynamic classes, with different sets of properties.
      *
-     * @return the set of properties for this DynaClass
+     * @return the name of the DynaClass
      */
     @Override
-    public DynaProperty[] getDynaProperties() {
-        return properties.clone();
+    public String getName() {
+        return this.name;
     }
 
     /**
@@ -189,17 +200,6 @@ public class BasicDynaClass implements DynaClass, Serializable {
         }
     }
 
-    /**
-     * Gets the Class object we will use to create new instances in the
-     * {@code newInstance()} method.  This Class <strong>MUST</strong>
-     * implement the {@code DynaBean} interface.
-     *
-     * @return The class of the {@link DynaBean}
-     */
-    public Class<?> getDynaBeanClass() {
-        return this.dynaBeanClass;
-    }
-
     /**
      * Sets the Class object we will use to create new instances in the
      * {@code newInstance()} method.  This Class <strong>MUST</strong>
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanComparator.java b/src/main/java/org/apache/commons/beanutils2/BeanComparator.java
index 67859718..ce628f98 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanComparator.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanComparator.java
@@ -43,8 +43,57 @@ import java.util.Comparator;
  */
 public class BeanComparator<T, V> implements Comparator<T>, Serializable {
 
+    /**
+     * A {@link Comparator Comparator} that compares {@link Comparable Comparable} objects.
+     * <p>
+     * This Comparator is useful, for example, for enforcing the natural order in custom implementations of
+     * {@link java.util.SortedSet SortedSet} and {@link java.util.SortedMap SortedMap}.
+     * </p>
+     *
+     * @param <E> the type of objects compared by this comparator
+     * @see java.util.Collections#reverseOrder()
+     */
+    private static class NaturalOrderComparator<E extends Comparable<? super E>>
+            implements Comparator<E>, Serializable {
+
+        /** Serialization version. */
+        private static final long serialVersionUID = -291439688585137865L;
+
+        /** The singleton instance. */
+        @SuppressWarnings("rawtypes")
+        public static final NaturalOrderComparator INSTANCE = new NaturalOrderComparator();
+
+        /**
+         * Private constructor to prevent instantiation. Only use INSTANCE.
+         */
+        private NaturalOrderComparator() {
+        }
+
+        /**
+         * Compare the two {@link Comparable Comparable} arguments. This method is equivalent to:
+         *
+         * <pre>
+         * ((Comparable) obj1).compareTo(obj2)
+         * </pre>
+         */
+        @Override
+        public int compare(final E obj1, final E obj2) {
+            return obj1.compareTo(obj2);
+        }
+
+        @Override
+        public boolean equals(final Object object) {
+            return this == object || null != object && object.getClass().equals(this.getClass());
+        }
+
+        @Override
+        public int hashCode() {
+            return "NaturalOrderComparator".hashCode();
+        }
+    }
     private static final long serialVersionUID = 1L;
     private String property;
+
     private final Comparator<V> comparator;
 
     /**
@@ -97,34 +146,6 @@ public class BeanComparator<T, V> implements Comparator<T>, Serializable {
         this.comparator = comparator != null ? comparator : NaturalOrderComparator.INSTANCE;
     }
 
-    /**
-     * Sets the method to be called to compare two JavaBeans
-     *
-     * @param property String method name to call to compare If the property passed in is null then the actual objects
-     *        will be compared
-     */
-    public void setProperty(final String property) {
-        this.property = property;
-    }
-
-    /**
-     * Gets the property attribute of the BeanComparator
-     *
-     * @return String method name to call to compare. A null value indicates that the actual objects will be compared
-     */
-    public String getProperty() {
-        return property;
-    }
-
-    /**
-     * Gets the Comparator being used to compare beans.
-     *
-     * @return the Comparator being used to compare beans
-     */
-    public Comparator<V> getComparator() {
-        return comparator;
-    }
-
     /**
      * Compare two JavaBeans by their shared property. If {@link #getProperty} is null then the actual objects will be
      * compared.
@@ -181,6 +202,24 @@ public class BeanComparator<T, V> implements Comparator<T>, Serializable {
         return true;
     }
 
+    /**
+     * Gets the Comparator being used to compare beans.
+     *
+     * @return the Comparator being used to compare beans
+     */
+    public Comparator<V> getComparator() {
+        return comparator;
+    }
+
+    /**
+     * Gets the property attribute of the BeanComparator
+     *
+     * @return String method name to call to compare. A null value indicates that the actual objects will be compared
+     */
+    public String getProperty() {
+        return property;
+    }
+
     /**
      * Hashcode compatible with equals.
      *
@@ -205,51 +244,12 @@ public class BeanComparator<T, V> implements Comparator<T>, Serializable {
     }
 
     /**
-     * A {@link Comparator Comparator} that compares {@link Comparable Comparable} objects.
-     * <p>
-     * This Comparator is useful, for example, for enforcing the natural order in custom implementations of
-     * {@link java.util.SortedSet SortedSet} and {@link java.util.SortedMap SortedMap}.
-     * </p>
+     * Sets the method to be called to compare two JavaBeans
      *
-     * @param <E> the type of objects compared by this comparator
-     * @see java.util.Collections#reverseOrder()
+     * @param property String method name to call to compare If the property passed in is null then the actual objects
+     *        will be compared
      */
-    private static class NaturalOrderComparator<E extends Comparable<? super E>>
-            implements Comparator<E>, Serializable {
-
-        /** Serialization version. */
-        private static final long serialVersionUID = -291439688585137865L;
-
-        /** The singleton instance. */
-        @SuppressWarnings("rawtypes")
-        public static final NaturalOrderComparator INSTANCE = new NaturalOrderComparator();
-
-        /**
-         * Private constructor to prevent instantiation. Only use INSTANCE.
-         */
-        private NaturalOrderComparator() {
-        }
-
-        /**
-         * Compare the two {@link Comparable Comparable} arguments. This method is equivalent to:
-         *
-         * <pre>
-         * ((Comparable) obj1).compareTo(obj2)
-         * </pre>
-         */
-        @Override
-        public int compare(final E obj1, final E obj2) {
-            return obj1.compareTo(obj2);
-        }
-
-        @Override
-        public int hashCode() {
-            return "NaturalOrderComparator".hashCode();
-        }
-
-        @Override
-        public boolean equals(final Object object) {
-            return this == object || null != object && object.getClass().equals(this.getClass());
-        }
+    public void setProperty(final String property) {
+        this.property = property;
     }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanIntrospectionData.java b/src/main/java/org/apache/commons/beanutils2/BeanIntrospectionData.java
index f4992806..7ca1c382 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanIntrospectionData.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanIntrospectionData.java
@@ -42,6 +42,26 @@ import java.util.Map;
  * @since 1.9.1
  */
 class BeanIntrospectionData {
+    /**
+     * Initializes the map with the names of the write methods for the supported
+     * properties. The method names - if defined - need to be stored separately because
+     * they may get lost when the GC claims soft references used by the
+     * {@code PropertyDescriptor} objects.
+     *
+     * @param descs the array with the descriptors of the available properties
+     * @return the map with the names of write methods for properties
+     */
+    private static Map<String, String> setUpWriteMethodNames(final PropertyDescriptor[] descs) {
+        final Map<String, String> methods = new HashMap<>();
+        for (final PropertyDescriptor pd : descs) {
+            final Method method = pd.getWriteMethod();
+            if (method != null) {
+                methods.put(pd.getName(), method.getName());
+            }
+        }
+        return methods;
+    }
+
     /** An array with property descriptors for the managed bean class. */
     private final PropertyDescriptor[] descriptors;
 
@@ -70,15 +90,6 @@ class BeanIntrospectionData {
         writeMethodNames = writeMethNames;
     }
 
-    /**
-     * Returns the array with property descriptors.
-     *
-     * @return the property descriptors for the associated bean class
-     */
-    public PropertyDescriptor[] getDescriptors() {
-        return descriptors;
-    }
-
     /**
      * Returns the {@code PropertyDescriptor} for the property with the specified name. If
      * this property is unknown, result is <b>null</b>.
@@ -95,6 +106,15 @@ class BeanIntrospectionData {
         return null;
     }
 
+    /**
+     * Returns the array with property descriptors.
+     *
+     * @return the property descriptors for the associated bean class
+     */
+    public PropertyDescriptor[] getDescriptors() {
+        return descriptors;
+    }
+
     /**
      * Returns the write method for the property determined by the given
      * {@code PropertyDescriptor}. This information is normally available in the
@@ -127,24 +147,4 @@ class BeanIntrospectionData {
 
         return method;
     }
-
-    /**
-     * Initializes the map with the names of the write methods for the supported
-     * properties. The method names - if defined - need to be stored separately because
-     * they may get lost when the GC claims soft references used by the
-     * {@code PropertyDescriptor} objects.
-     *
-     * @param descs the array with the descriptors of the available properties
-     * @return the map with the names of write methods for properties
-     */
-    private static Map<String, String> setUpWriteMethodNames(final PropertyDescriptor[] descs) {
-        final Map<String, String> methods = new HashMap<>();
-        for (final PropertyDescriptor pd : descs) {
-            final Method method = pd.getWriteMethod();
-            if (method != null) {
-                methods.put(pd.getName(), method.getName());
-            }
-        }
-        return methods;
-    }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanMap.java b/src/main/java/org/apache/commons/beanutils2/BeanMap.java
index 2fcfe9af..e6cb7a14 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanMap.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanMap.java
@@ -43,17 +43,48 @@ import java.util.function.Function;
  */
 public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
 
-    private transient Object bean;
+    /**
+     * Map entry used by {@link BeanMap}.
+     */
+    protected static class Entry extends AbstractMap.SimpleEntry<String, Object> {
 
-    private final transient HashMap<String, Method> readMethods = new HashMap<>();
-    private final transient HashMap<String, Method> writeMethods = new HashMap<>();
-    private final transient HashMap<String, Class<? extends Object>> types = new HashMap<>();
+        private static final long serialVersionUID = 1L;
+        private final BeanMap owner;
+
+        /**
+         * Constructs a new {@code Entry}.
+         *
+         * @param owner the BeanMap this entry belongs to
+         * @param key the key for this entry
+         * @param value the value for this entry
+         */
+        protected Entry(final BeanMap owner, final String key, final Object value) {
+            super(key, value);
+            this.owner = owner;
+        }
+
+        /**
+         * Sets the value.
+         *
+         * @param value the new value for the entry
+         * @return the old value for the entry
+         */
+        @Override
+        public Object setValue(final Object value) {
+            final String key = getKey();
+            final Object oldValue = owner.get(key);
+
+            owner.put(key, value);
+            final Object newValue = owner.get(key);
+            super.setValue(newValue);
+            return oldValue;
+        }
+    }
 
     /**
      * An empty array. Used to invoke accessors via reflection.
      */
     public static final Object[] NULL_ARGUMENTS = {};
-
     /**
      * Maps primitive Class types to transformers. The transformer transform strings into the appropriate primitive
      * wrapper.
@@ -61,7 +92,6 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
      * N.B. private & unmodifiable replacement for the (public & static) defaultTransformers instance.
      */
     private static final Map<Class<? extends Object>, Function<?, ?>> typeTransformers = Collections.unmodifiableMap(createTypeTransformers());
-
     private static Map<Class<? extends Object>, Function<?, ?>> createTypeTransformers() {
         final Map<Class<? extends Object>, Function<?, ?>> defTransformers = new HashMap<>();
         defTransformers.put(Boolean.TYPE, input -> Boolean.valueOf(input.toString()));
@@ -75,15 +105,26 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
         return defTransformers;
     }
 
+    private transient Object bean;
+
+    private final transient HashMap<String, Method> readMethods = new HashMap<>();
+
+    private final transient HashMap<String, Method> writeMethods = new HashMap<>();
+
     // Constructors
 
 
+    private final transient HashMap<String, Class<? extends Object>> types = new HashMap<>();
+
     /**
      * Constructs a new empty {@code BeanMap}.
      */
     public BeanMap() {
     }
 
+    // Map interface
+
+
     /**
      * Constructs a new {@code BeanMap} that operates on the specified bean. If the given bean is
      * {@code null}, then this map will be empty.
@@ -95,17 +136,28 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
         initialize();
     }
 
-    // Map interface
-
-
     /**
-     * Renders a string representation of this object.
-     *
-     * @return a {@code String} representation of this object
+     * This method reinitializes the bean map to have default values for the bean's properties. This is accomplished by
+     * constructing a new instance of the bean which the map uses as its underlying data source. This behavior for
+     * {@code clear()} differs from the Map contract in that the mappings are not actually removed from the map
+     * (the mappings for a BeanMap are fixed).
      */
     @Override
-    public String toString() {
-        return "BeanMap<" + bean + ">";
+    public void clear() {
+        if (bean == null) {
+            return;
+        }
+
+        Class<? extends Object> beanClass = null;
+        try {
+            beanClass = bean.getClass();
+            bean = beanClass.newInstance();
+        } catch (final Exception e) {
+            final UnsupportedOperationException uoe = new UnsupportedOperationException(
+                    "Could not create new instance of class: " + beanClass);
+            BeanUtils.initCause(uoe, e);
+            throw uoe;
+        }
     }
 
     /**
@@ -179,44 +231,6 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
         return newMap;
     }
 
-    /**
-     * Puts all of the writable properties from the given BeanMap into this BeanMap. Read-only and Write-only properties
-     * will be ignored.
-     *
-     * @param map the BeanMap whose properties to put
-     */
-    public void putAllWriteable(final BeanMap map) {
-        map.readMethods.keySet().forEach(key -> {
-            if (getWriteMethod(key) != null) {
-                this.put(key, map.get(key));
-            }
-        });
-    }
-
-    /**
-     * This method reinitializes the bean map to have default values for the bean's properties. This is accomplished by
-     * constructing a new instance of the bean which the map uses as its underlying data source. This behavior for
-     * {@code clear()} differs from the Map contract in that the mappings are not actually removed from the map
-     * (the mappings for a BeanMap are fixed).
-     */
-    @Override
-    public void clear() {
-        if (bean == null) {
-            return;
-        }
-
-        Class<? extends Object> beanClass = null;
-        try {
-            beanClass = bean.getClass();
-            bean = beanClass.newInstance();
-        } catch (final Exception e) {
-            final UnsupportedOperationException uoe = new UnsupportedOperationException(
-                    "Could not create new instance of class: " + beanClass);
-            BeanUtils.initCause(uoe, e);
-            throw uoe;
-        }
-    }
-
     /**
      * Returns true if the bean defines a property with the given name.
      * <p>
@@ -248,94 +262,117 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
     }
 
     /**
-     * Returns the value of the bean's property with the given name.
-     * <p>
-     * The given name must be a {@link String} and must not be null; otherwise, this method returns {@code null}.
-     * If the bean defines a property with the given name, the value of that property is returned. Otherwise,
-     * {@code null} is returned.
-     * <p>
-     * Write-only properties will not be matched as the test operates against property read methods.
+     * Converts the given value to the given type. First, reflection is used to find a public constructor declared by
+     * the given class that takes one argument, which must be the precise type of the given value. If such a constructor
+     * is found, a new object is created by passing the given value to that constructor, and the newly constructed
+     * object is returned.
+     * <P>
      *
-     * @param name the name of the property whose value to return
-     * @return the value of the property with that name
+     * If no such constructor exists, and the given type is a primitive type, then the given value is converted to a
+     * string using its {@link Object#toString() toString()} method, and that string is parsed into the correct
+     * primitive type using, for instance, {@link Integer#valueOf(String)} to convert the string into an
+     * {@code int}.
+     * <P>
+     *
+     * If no special constructor exists and the given type is not a primitive type, this method returns the original
+     * value.
+     *
+     * @param <R> The return type.
+     * @param newType the type to convert the value to
+     * @param value the value to convert
+     * @return the converted value
+     * @throws NumberFormatException if newType is a primitive type, and the string representation of the given value
+     *         cannot be converted to that type
+     * @throws InstantiationException if the constructor found with reflection raises it
+     * @throws InvocationTargetException if the constructor found with reflection raises it
+     * @throws IllegalAccessException never
+     * @throws IllegalArgumentException never
      */
-    @Override
-    public Object get(final Object name) {
-        if (bean != null) {
-            final Method method = getReadMethod(name);
-            if (method != null) {
-                try {
-                    return method.invoke(bean, NULL_ARGUMENTS);
-                } catch (final IllegalAccessException | NullPointerException |
-                               InvocationTargetException | IllegalArgumentException e) {
-                    logWarn(e);
-                }
+    protected <R> Object convertType(final Class<R> newType, final Object value)
+            throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+
+        // try call constructor
+        try {
+            final Constructor<R> constructor = newType.getConstructor(value.getClass());
+            return constructor.newInstance(value);
+        } catch (final NoSuchMethodException e) {
+            // try using the transformers
+            final Function<Object, R> transformer = getTypeTransformer(newType);
+            if (transformer != null) {
+                return transformer.apply(value);
             }
+            return value;
         }
-        return null;
     }
 
     /**
-     * Sets the bean property with the given name to the given value.
+     * Creates an array of parameters to pass to the given mutator method. If the given object is not the right type to
+     * pass to the method directly, it will be converted using {@link #convertType(Class,Object)}.
      *
-     * @param name the name of the property to set
-     * @param value the value to set that property to
-     * @return the previous value of that property
-     * @throws IllegalArgumentException if the given name is null; if the given name is not a {@link String}; if the
-     *         bean doesn't define a property with that name; or if the bean property with that name is read-only
+     * @param method the mutator method
+     * @param value the value to pass to the mutator method
+     * @return an array containing one object that is either the given value or a transformed value
+     * @throws IllegalAccessException if {@link #convertType(Class,Object)} raises it
+     * @throws IllegalArgumentException if any other exception is raised by {@link #convertType(Class,Object)}
      * @throws ClassCastException if an error occurs creating the method args
      */
-    @Override
-    public Object put(final String name, final Object value) throws IllegalArgumentException, ClassCastException {
-        if (bean != null) {
-            final Object oldValue = get(name);
-            final Method method = getWriteMethod(name);
-            if (method == null) {
-                throw new IllegalArgumentException(
-                        "The bean of type: " + bean.getClass().getName() + " has no property called: " + name);
-            }
-            try {
-                final Object[] arguments = createWriteMethodArguments(method, value);
-                method.invoke(bean, arguments);
-
-                final Object newValue = get(name);
-                firePropertyChange(name, oldValue, newValue);
-            } catch (final InvocationTargetException | IllegalAccessException e) {
-                final IllegalArgumentException iae = new IllegalArgumentException(e.getMessage());
-                if (!BeanUtils.initCause(iae, e)) {
-                    logInfo(e);
+    protected Object[] createWriteMethodArguments(final Method method, Object value)
+            throws IllegalAccessException, ClassCastException {
+        try {
+            if (value != null) {
+                final Class<? extends Object>[] paramTypes = method.getParameterTypes();
+                if (paramTypes != null && paramTypes.length > 0) {
+                    final Class<? extends Object> paramType = paramTypes[0];
+                    if (!paramType.isAssignableFrom(value.getClass())) {
+                        value = convertType(paramType, value);
+                    }
                 }
-                throw iae;
             }
-            return oldValue;
+            final Object[] answer = { value };
+            return answer;
+        } catch (final InvocationTargetException e) {
+            final IllegalArgumentException iae = new IllegalArgumentException(e.getMessage());
+            if (!BeanUtils.initCause(iae, e)) {
+                logInfo(e);
+            }
+            throw iae;
+        } catch (final InstantiationException e) {
+            final IllegalArgumentException iae = new IllegalArgumentException(e.getMessage());
+            if (!BeanUtils.initCause(iae, e)) {
+                logInfo(e);
+            }
+            BeanUtils.initCause(iae, e);
+            throw iae;
         }
-        return null;
     }
 
     /**
-     * Returns the number of properties defined by the bean.
+     * Convenience method for getting an iterator over the entries.
      *
-     * @return the number of properties defined by the bean
+     * @return an iterator over the entries
      */
-    @Override
-    public int size() {
-        return readMethods.size();
-    }
+    public Iterator<Map.Entry<String, Object>> entryIterator() {
+        final Iterator<String> iter = keyIterator();
+        return new Iterator<Map.Entry<String, Object>>() {
+            @Override
+            public boolean hasNext() {
+                return iter.hasNext();
+            }
 
-    /**
-     * Get the keys for this BeanMap.
-     * <p>
-     * Write-only properties are <b>not</b> included in the returned set of property names, although it is possible to
-     * set their value and to get their type.
-     *
-     * @return BeanMap keys. The Set returned by this method is not modifiable.
-     */
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    // The set actually contains strings; however, because it cannot be
-    // modified there is no danger in selling it as Set<Object>
-    @Override
-    public Set<String> keySet() {
-        return Collections.unmodifiableSet((Set) readMethods.keySet());
+            @Override
+            public Map.Entry<String, Object> next() {
+                final String key = iter.next();
+                final Object value = get(key);
+                // This should not cause any problems; the key is actually a
+                // string, but it does no harm to expose it as Object
+                return new Entry(BeanMap.this, key, value);
+            }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException("remove() not supported for BeanMap");
+            }
+        };
     }
 
     /**
@@ -361,99 +398,45 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
     }
 
     /**
-     * Returns the values for the BeanMap.
-     *
-     * @return values for the BeanMap. The returned collection is not modifiable.
-     */
-    @Override
-    public Collection<Object> values() {
-        final ArrayList<Object> answer = new ArrayList<>(readMethods.size());
-        valueIterator().forEachRemaining(answer::add);
-        return Collections.unmodifiableList(answer);
-    }
-
-    // Helper methods
-
-
-    /**
-     * Returns the type of the property with the given name.
+     * Called during a successful {@link #put(String,Object)} operation. Default implementation does nothing. Override
+     * to be notified of property changes in the bean caused by this map.
      *
-     * @param name the name of the property
-     * @return the type of the property, or {@code null} if no such property exists
+     * @param key the name of the property that changed
+     * @param oldValue the old value for that property
+     * @param newValue the new value for that property
      */
-    public Class<?> getType(final String name) {
-        return types.get(name);
+    protected void firePropertyChange(final Object key, final Object oldValue, final Object newValue) {
+        // noop
     }
 
     /**
-     * Convenience method for getting an iterator over the keys.
+     * Returns the value of the bean's property with the given name.
      * <p>
-     * Write-only properties will not be returned in the iterator.
-     *
-     * @return an iterator over the keys
-     */
-    public Iterator<String> keyIterator() {
-        return readMethods.keySet().iterator();
-    }
-
-    /**
-     * Convenience method for getting an iterator over the values.
-     *
-     * @return an iterator over the values
-     */
-    public Iterator<Object> valueIterator() {
-        final Iterator<?> iter = keyIterator();
-        return new Iterator<Object>() {
-            @Override
-            public boolean hasNext() {
-                return iter.hasNext();
-            }
-
-            @Override
-            public Object next() {
-                final Object key = iter.next();
-                return get(key);
-            }
-
-            @Override
-            public void remove() {
-                throw new UnsupportedOperationException("remove() not supported for BeanMap");
-            }
-        };
-    }
-
-    /**
-     * Convenience method for getting an iterator over the entries.
+     * The given name must be a {@link String} and must not be null; otherwise, this method returns {@code null}.
+     * If the bean defines a property with the given name, the value of that property is returned. Otherwise,
+     * {@code null} is returned.
+     * <p>
+     * Write-only properties will not be matched as the test operates against property read methods.
      *
-     * @return an iterator over the entries
+     * @param name the name of the property whose value to return
+     * @return the value of the property with that name
      */
-    public Iterator<Map.Entry<String, Object>> entryIterator() {
-        final Iterator<String> iter = keyIterator();
-        return new Iterator<Map.Entry<String, Object>>() {
-            @Override
-            public boolean hasNext() {
-                return iter.hasNext();
-            }
-
-            @Override
-            public Map.Entry<String, Object> next() {
-                final String key = iter.next();
-                final Object value = get(key);
-                // This should not cause any problems; the key is actually a
-                // string, but it does no harm to expose it as Object
-                return new Entry(BeanMap.this, key, value);
-            }
-
-            @Override
-            public void remove() {
-                throw new UnsupportedOperationException("remove() not supported for BeanMap");
+    @Override
+    public Object get(final Object name) {
+        if (bean != null) {
+            final Method method = getReadMethod(name);
+            if (method != null) {
+                try {
+                    return method.invoke(bean, NULL_ARGUMENTS);
+                } catch (final IllegalAccessException | NullPointerException |
+                               InvocationTargetException | IllegalArgumentException e) {
+                    logWarn(e);
+                }
             }
-        };
+        }
+        return null;
     }
 
-    // Properties
-
-
     /**
      * Returns the bean currently being operated on. The return value may be null if this map is empty.
      *
@@ -463,14 +446,18 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
         return bean;
     }
 
+    // Helper methods
+
+
     /**
-     * Sets the bean to be operated on by this map. The given value may be null, in which case this map will be empty.
+     * Returns the accessor for the property with the given name.
      *
-     * @param newBean the new bean to operate on
+     * @param name the name of the property
+     * @return null if the name is null; null if the name is not a {@link String}; null if no such property exists; or
+     *         the accessor method for that property
      */
-    public void setBean(final Object newBean) {
-        bean = newBean;
-        reinitialise();
+    protected Method getReadMethod(final Object name) {
+        return readMethods.get(name);
     }
 
     /**
@@ -484,29 +471,29 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
     }
 
     /**
-     * Returns the mutator for the property with the given name.
+     * Returns the type of the property with the given name.
      *
      * @param name the name of the property
-     * @return the mutator method for the property, or null
+     * @return the type of the property, or {@code null} if no such property exists
      */
-    public Method getWriteMethod(final String name) {
-        return writeMethods.get(name);
+    public Class<?> getType(final String name) {
+        return types.get(name);
     }
 
-    // Implementation methods
-
-
     /**
-     * Returns the accessor for the property with the given name.
+     * Returns a transformer for the given primitive type.
      *
-     * @param name the name of the property
-     * @return null if the name is null; null if the name is not a {@link String}; null if no such property exists; or
-     *         the accessor method for that property
+     * @param <R> The transformer result type.
+     * @param type the primitive type whose transformer to return
+     * @return a transformer that will convert strings into that type, or null if the given type is not a primitive type
      */
-    protected Method getReadMethod(final Object name) {
-        return readMethods.get(name);
+    protected <R> Function<Object, R> getTypeTransformer(final Class<R> type) {
+        return (Function<Object, R>) typeTransformers.get(type);
     }
 
+    // Properties
+
+
     /**
      * Returns the mutator for the property with the given name.
      *
@@ -519,13 +506,13 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
     }
 
     /**
-     * Reinitializes this bean. Called during {@link #setBean(Object)}. Does introspection to find properties.
+     * Returns the mutator for the property with the given name.
+     *
+     * @param name the name of the property
+     * @return the mutator method for the property, or null
      */
-    protected void reinitialise() {
-        readMethods.clear();
-        writeMethods.clear();
-        types.clear();
-        initialize();
+    public Method getWriteMethod(final String name) {
+        return writeMethods.get(name);
     }
 
     private void initialize() {
@@ -562,172 +549,185 @@ public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
     }
 
     /**
-     * Called during a successful {@link #put(String,Object)} operation. Default implementation does nothing. Override
-     * to be notified of property changes in the bean caused by this map.
+     * Convenience method for getting an iterator over the keys.
+     * <p>
+     * Write-only properties will not be returned in the iterator.
      *
-     * @param key the name of the property that changed
-     * @param oldValue the old value for that property
-     * @param newValue the new value for that property
+     * @return an iterator over the keys
      */
-    protected void firePropertyChange(final Object key, final Object oldValue, final Object newValue) {
-        // noop
+    public Iterator<String> keyIterator() {
+        return readMethods.keySet().iterator();
     }
 
-    // Implementation classes
+    // Implementation methods
 
 
     /**
-     * Map entry used by {@link BeanMap}.
+     * Get the keys for this BeanMap.
+     * <p>
+     * Write-only properties are <b>not</b> included in the returned set of property names, although it is possible to
+     * set their value and to get their type.
+     *
+     * @return BeanMap keys. The Set returned by this method is not modifiable.
      */
-    protected static class Entry extends AbstractMap.SimpleEntry<String, Object> {
-
-        private static final long serialVersionUID = 1L;
-        private final BeanMap owner;
-
-        /**
-         * Constructs a new {@code Entry}.
-         *
-         * @param owner the BeanMap this entry belongs to
-         * @param key the key for this entry
-         * @param value the value for this entry
-         */
-        protected Entry(final BeanMap owner, final String key, final Object value) {
-            super(key, value);
-            this.owner = owner;
-        }
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    // The set actually contains strings; however, because it cannot be
+    // modified there is no danger in selling it as Set<Object>
+    @Override
+    public Set<String> keySet() {
+        return Collections.unmodifiableSet((Set) readMethods.keySet());
+    }
 
-        /**
-         * Sets the value.
-         *
-         * @param value the new value for the entry
-         * @return the old value for the entry
-         */
-        @Override
-        public Object setValue(final Object value) {
-            final String key = getKey();
-            final Object oldValue = owner.get(key);
+    /**
+     * Logs the given exception to {@code System.out}. Used to display warnings while accessing/mutating the bean.
+     *
+     * @param ex the exception to log
+     */
+    protected void logInfo(final Exception ex) {
+        // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
+        System.out.println("INFO: Exception: " + ex);
+    }
 
-            owner.put(key, value);
-            final Object newValue = owner.get(key);
-            super.setValue(newValue);
-            return oldValue;
-        }
+    /**
+     * Logs the given exception to {@code System.err}. Used to display errors while accessing/mutating the bean.
+     *
+     * @param ex the exception to log
+     */
+    protected void logWarn(final Exception ex) {
+        // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
+        System.out.println("WARN: Exception: " + ex);
+        ex.printStackTrace();
     }
 
     /**
-     * Creates an array of parameters to pass to the given mutator method. If the given object is not the right type to
-     * pass to the method directly, it will be converted using {@link #convertType(Class,Object)}.
+     * Sets the bean property with the given name to the given value.
      *
-     * @param method the mutator method
-     * @param value the value to pass to the mutator method
-     * @return an array containing one object that is either the given value or a transformed value
-     * @throws IllegalAccessException if {@link #convertType(Class,Object)} raises it
-     * @throws IllegalArgumentException if any other exception is raised by {@link #convertType(Class,Object)}
+     * @param name the name of the property to set
+     * @param value the value to set that property to
+     * @return the previous value of that property
+     * @throws IllegalArgumentException if the given name is null; if the given name is not a {@link String}; if the
+     *         bean doesn't define a property with that name; or if the bean property with that name is read-only
      * @throws ClassCastException if an error occurs creating the method args
      */
-    protected Object[] createWriteMethodArguments(final Method method, Object value)
-            throws IllegalAccessException, ClassCastException {
-        try {
-            if (value != null) {
-                final Class<? extends Object>[] paramTypes = method.getParameterTypes();
-                if (paramTypes != null && paramTypes.length > 0) {
-                    final Class<? extends Object> paramType = paramTypes[0];
-                    if (!paramType.isAssignableFrom(value.getClass())) {
-                        value = convertType(paramType, value);
-                    }
-                }
-            }
-            final Object[] answer = { value };
-            return answer;
-        } catch (final InvocationTargetException e) {
-            final IllegalArgumentException iae = new IllegalArgumentException(e.getMessage());
-            if (!BeanUtils.initCause(iae, e)) {
-                logInfo(e);
+    @Override
+    public Object put(final String name, final Object value) throws IllegalArgumentException, ClassCastException {
+        if (bean != null) {
+            final Object oldValue = get(name);
+            final Method method = getWriteMethod(name);
+            if (method == null) {
+                throw new IllegalArgumentException(
+                        "The bean of type: " + bean.getClass().getName() + " has no property called: " + name);
             }
-            throw iae;
-        } catch (final InstantiationException e) {
-            final IllegalArgumentException iae = new IllegalArgumentException(e.getMessage());
-            if (!BeanUtils.initCause(iae, e)) {
-                logInfo(e);
+            try {
+                final Object[] arguments = createWriteMethodArguments(method, value);
+                method.invoke(bean, arguments);
+
+                final Object newValue = get(name);
+                firePropertyChange(name, oldValue, newValue);
+            } catch (final InvocationTargetException | IllegalAccessException e) {
+                final IllegalArgumentException iae = new IllegalArgumentException(e.getMessage());
+                if (!BeanUtils.initCause(iae, e)) {
+                    logInfo(e);
+                }
+                throw iae;
             }
-            BeanUtils.initCause(iae, e);
-            throw iae;
+            return oldValue;
         }
+        return null;
     }
 
     /**
-     * Converts the given value to the given type. First, reflection is used to find a public constructor declared by
-     * the given class that takes one argument, which must be the precise type of the given value. If such a constructor
-     * is found, a new object is created by passing the given value to that constructor, and the newly constructed
-     * object is returned.
-     * <P>
-     *
-     * If no such constructor exists, and the given type is a primitive type, then the given value is converted to a
-     * string using its {@link Object#toString() toString()} method, and that string is parsed into the correct
-     * primitive type using, for instance, {@link Integer#valueOf(String)} to convert the string into an
-     * {@code int}.
-     * <P>
+     * Puts all of the writable properties from the given BeanMap into this BeanMap. Read-only and Write-only properties
+     * will be ignored.
      *
-     * If no special constructor exists and the given type is not a primitive type, this method returns the original
-     * value.
+     * @param map the BeanMap whose properties to put
+     */
+    public void putAllWriteable(final BeanMap map) {
+        map.readMethods.keySet().forEach(key -> {
+            if (getWriteMethod(key) != null) {
+                this.put(key, map.get(key));
+            }
+        });
+    }
+
+    // Implementation classes
+
+
+    /**
+     * Reinitializes this bean. Called during {@link #setBean(Object)}. Does introspection to find properties.
+     */
+    protected void reinitialise() {
+        readMethods.clear();
+        writeMethods.clear();
+        types.clear();
+        initialize();
+    }
+
+    /**
+     * Sets the bean to be operated on by this map. The given value may be null, in which case this map will be empty.
      *
-     * @param <R> The return type.
-     * @param newType the type to convert the value to
-     * @param value the value to convert
-     * @return the converted value
-     * @throws NumberFormatException if newType is a primitive type, and the string representation of the given value
-     *         cannot be converted to that type
-     * @throws InstantiationException if the constructor found with reflection raises it
-     * @throws InvocationTargetException if the constructor found with reflection raises it
-     * @throws IllegalAccessException never
-     * @throws IllegalArgumentException never
+     * @param newBean the new bean to operate on
      */
-    protected <R> Object convertType(final Class<R> newType, final Object value)
-            throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+    public void setBean(final Object newBean) {
+        bean = newBean;
+        reinitialise();
+    }
 
-        // try call constructor
-        try {
-            final Constructor<R> constructor = newType.getConstructor(value.getClass());
-            return constructor.newInstance(value);
-        } catch (final NoSuchMethodException e) {
-            // try using the transformers
-            final Function<Object, R> transformer = getTypeTransformer(newType);
-            if (transformer != null) {
-                return transformer.apply(value);
-            }
-            return value;
-        }
+    /**
+     * Returns the number of properties defined by the bean.
+     *
+     * @return the number of properties defined by the bean
+     */
+    @Override
+    public int size() {
+        return readMethods.size();
     }
 
     /**
-     * Returns a transformer for the given primitive type.
+     * Renders a string representation of this object.
      *
-     * @param <R> The transformer result type.
-     * @param type the primitive type whose transformer to return
-     * @return a transformer that will convert strings into that type, or null if the given type is not a primitive type
+     * @return a {@code String} representation of this object
      */
-    protected <R> Function<Object, R> getTypeTransformer(final Class<R> type) {
-        return (Function<Object, R>) typeTransformers.get(type);
+    @Override
+    public String toString() {
+        return "BeanMap<" + bean + ">";
     }
 
     /**
-     * Logs the given exception to {@code System.out}. Used to display warnings while accessing/mutating the bean.
+     * Convenience method for getting an iterator over the values.
      *
-     * @param ex the exception to log
+     * @return an iterator over the values
      */
-    protected void logInfo(final Exception ex) {
-        // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
-        System.out.println("INFO: Exception: " + ex);
+    public Iterator<Object> valueIterator() {
+        final Iterator<?> iter = keyIterator();
+        return new Iterator<Object>() {
+            @Override
+            public boolean hasNext() {
+                return iter.hasNext();
+            }
+
+            @Override
+            public Object next() {
+                final Object key = iter.next();
+                return get(key);
+            }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException("remove() not supported for BeanMap");
+            }
+        };
     }
 
     /**
-     * Logs the given exception to {@code System.err}. Used to display errors while accessing/mutating the bean.
+     * Returns the values for the BeanMap.
      *
-     * @param ex the exception to log
+     * @return values for the BeanMap. The returned collection is not modifiable.
      */
-    protected void logWarn(final Exception ex) {
-        // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
-        System.out.println("WARN: Exception: " + ex);
-        ex.printStackTrace();
+    @Override
+    public Collection<Object> values() {
+        final ArrayList<Object> answer = new ArrayList<>(readMethods.size());
+        valueIterator().forEachRemaining(answer::add);
+        return Collections.unmodifiableList(answer);
     }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanPredicate.java b/src/main/java/org/apache/commons/beanutils2/BeanPredicate.java
index 17dd7325..6bf1a62a 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanPredicate.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanPredicate.java
@@ -53,6 +53,42 @@ public class BeanPredicate<T> implements Predicate<T> {
         this.predicate = predicate;
     }
 
+    /**
+     * Gets the {@code Predicate} to be applied to the value of the named property
+     * during {@link #test(Object)}.
+     * @return {@code Predicate}, not null
+     */
+    public Predicate<T> getPredicate() {
+        return predicate;
+    }
+
+    /**
+     * Gets the name of the property whose value is to be predicated.
+     * in the evaluation.
+     * @return the property name, not null
+     */
+    public String getPropertyName() {
+        return propertyName;
+    }
+
+    /**
+     * Sets the {@code Predicate} to be applied to the value of the named property
+     * during {@link #test(Object)}.
+     * @param predicate {@code Predicate}, not null
+     */
+    public void setPredicate(final Predicate<T> predicate) {
+        this.predicate = predicate;
+    }
+
+    /**
+     * Sets the name of the property whose value is to be predicated.
+     * @param propertyName the name of the property whose value is to be predicated,
+     * not null
+     */
+    public void setPropertyName(final String propertyName) {
+        this.propertyName = propertyName;
+    }
+
     /**
      * Evaluates the given object by applying the {@link #getPredicate()}
      * to a property value named by {@link #getPropertyName()}.
@@ -89,40 +125,4 @@ public class BeanPredicate<T> implements Predicate<T> {
         return evaluation;
     }
 
-    /**
-     * Gets the name of the property whose value is to be predicated.
-     * in the evaluation.
-     * @return the property name, not null
-     */
-    public String getPropertyName() {
-        return propertyName;
-    }
-
-    /**
-     * Sets the name of the property whose value is to be predicated.
-     * @param propertyName the name of the property whose value is to be predicated,
-     * not null
-     */
-    public void setPropertyName(final String propertyName) {
-        this.propertyName = propertyName;
-    }
-
-    /**
-     * Gets the {@code Predicate} to be applied to the value of the named property
-     * during {@link #test(Object)}.
-     * @return {@code Predicate}, not null
-     */
-    public Predicate<T> getPredicate() {
-        return predicate;
-    }
-
-    /**
-     * Sets the {@code Predicate} to be applied to the value of the named property
-     * during {@link #test(Object)}.
-     * @param predicate {@code Predicate}, not null
-     */
-    public void setPredicate(final Predicate<T> predicate) {
-        this.predicate = predicate;
-    }
-
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanPropertyValueEqualsPredicate.java b/src/main/java/org/apache/commons/beanutils2/BeanPropertyValueEqualsPredicate.java
index c6aea087..bf835551 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanPropertyValueEqualsPredicate.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanPropertyValueEqualsPredicate.java
@@ -174,6 +174,57 @@ public class BeanPropertyValueEqualsPredicate<T, V> implements Predicate<T> {
         this.ignoreNull = ignoreNull;
     }
 
+    /**
+     * Utility method which evaluates whether the actual property value equals the expected property
+     * value.
+     *
+     * @param expected The expected value.
+     * @param actual The actual value.
+     * @return True if they are equal; false otherwise.
+     */
+    protected boolean evaluateValue(final V expected, final Object actual) {
+        return Objects.equals(expected, actual);
+    }
+
+    /**
+     * Returns the name of the property which will be evaluated when this {@code Predicate} is
+     * executed.
+     *
+     * @return The name of the property which will be evaluated when this {@code Predicate} is
+     * executed.
+     */
+    public String getPropertyName() {
+        return propertyName;
+    }
+
+    /**
+     * Returns the value that the property specified by {@code propertyName} will be compared to
+     * when this {@code Predicate} executes.
+     *
+     * @return The value that the property specified by {@code propertyName} will be compared to
+     * when this {@code Predicate} executes.
+     */
+    public V getPropertyValue() {
+        return propertyValue;
+    }
+
+    /**
+     * Returns the flag which determines whether {@code null} objects in the property path will
+     * generate an {@code IllegalArgumentException</code> or not. If set to <code>true} then
+     * if any objects in the property path evaluate to {@code null} then the
+     * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged but
+     * not re-thrown and {@code false</code> will be returned.  If set to <code>false} then if
+     * any objects in the property path evaluate to {@code null} then the
+     * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged and
+     * re-thrown.
+     *
+     * @return The flag which determines whether {@code null} objects in the property path will
+     * generate an {@code IllegalArgumentException} or not.
+     */
+    public boolean isIgnoreNull() {
+        return ignoreNull;
+    }
+
     /**
      * Evaluates the object provided against the criteria specified when this
      * {@code BeanPropertyValueEqualsPredicate} was constructed.  Equality is based on
@@ -236,55 +287,4 @@ public class BeanPropertyValueEqualsPredicate<T, V> implements Predicate<T> {
 
         return evaluation;
     }
-
-    /**
-     * Utility method which evaluates whether the actual property value equals the expected property
-     * value.
-     *
-     * @param expected The expected value.
-     * @param actual The actual value.
-     * @return True if they are equal; false otherwise.
-     */
-    protected boolean evaluateValue(final V expected, final Object actual) {
-        return Objects.equals(expected, actual);
-    }
-
-    /**
-     * Returns the name of the property which will be evaluated when this {@code Predicate} is
-     * executed.
-     *
-     * @return The name of the property which will be evaluated when this {@code Predicate} is
-     * executed.
-     */
-    public String getPropertyName() {
-        return propertyName;
-    }
-
-    /**
-     * Returns the value that the property specified by {@code propertyName} will be compared to
-     * when this {@code Predicate} executes.
-     *
-     * @return The value that the property specified by {@code propertyName} will be compared to
-     * when this {@code Predicate} executes.
-     */
-    public V getPropertyValue() {
-        return propertyValue;
-    }
-
-    /**
-     * Returns the flag which determines whether {@code null} objects in the property path will
-     * generate an {@code IllegalArgumentException</code> or not. If set to <code>true} then
-     * if any objects in the property path evaluate to {@code null} then the
-     * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged but
-     * not re-thrown and {@code false</code> will be returned.  If set to <code>false} then if
-     * any objects in the property path evaluate to {@code null} then the
-     * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged and
-     * re-thrown.
-     *
-     * @return The flag which determines whether {@code null} objects in the property path will
-     * generate an {@code IllegalArgumentException} or not.
-     */
-    public boolean isIgnoreNull() {
-        return ignoreNull;
-    }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanUtils.java b/src/main/java/org/apache/commons/beanutils2/BeanUtils.java
index ce128e99..14e79764 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanUtils.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanUtils.java
@@ -35,6 +35,14 @@ import java.util.Map;
 
 public class BeanUtils {
 
+    /** An empty class array */
+    static final Class<?>[] EMPTY_CLASS_ARRAY = {};
+
+
+    /** An empty object array */
+    static final Object[] EMPTY_OBJECT_ARRAY = {};
+
+
     /**
      * <p>Clone a bean based on the available property getters and setters,
      * even if the bean class itself does not implement Cloneable.</p>
@@ -112,6 +120,18 @@ public class BeanUtils {
     }
 
 
+    /**
+     * Create a cache.
+     * @param <K> the key type of the cache
+     * @param <V> the value type of the cache
+     * @return a new cache
+     * @since 1.8.0
+     */
+    public static <K, V> Map<K, V> createCache() {
+        return new WeakFastHashMap<>();
+    }
+
+
     /**
      * <p>Return the entire set of properties for which the specified bean
      * provides a read method.</p>
@@ -334,7 +354,6 @@ public class BeanUtils {
 
     }
 
-
     /**
      * <p>Return the value of the specified simple property of the specified
      * bean, converted to a String.</p>
@@ -361,6 +380,17 @@ public class BeanUtils {
 
     }
 
+    /**
+     * If we're running on JDK 1.4 or later, initialize the cause for the given throwable.
+     *
+     * @param  throwable The throwable.
+     * @param  cause     The cause of the throwable.
+     * @return  true if the cause was initialized, otherwise false.
+     * @since 1.8.0
+     */
+    public static boolean initCause(final Throwable throwable, final Throwable cause) {
+        return BeanUtilsBean.getInstance().initCause(throwable, cause);
+    }
 
     /**
      * <p>Populate the JavaBeans properties of the specified bean, based on
@@ -384,7 +414,6 @@ public class BeanUtils {
         BeanUtilsBean.getInstance().populate(bean, properties);
     }
 
-
     /**
      * <p>Set the specified property value, performing type conversions as
      * required to conform to the type of the destination property.</p>
@@ -406,33 +435,4 @@ public class BeanUtils {
 
         BeanUtilsBean.getInstance().setProperty(bean, name, value);
     }
-
-    /**
-     * If we're running on JDK 1.4 or later, initialize the cause for the given throwable.
-     *
-     * @param  throwable The throwable.
-     * @param  cause     The cause of the throwable.
-     * @return  true if the cause was initialized, otherwise false.
-     * @since 1.8.0
-     */
-    public static boolean initCause(final Throwable throwable, final Throwable cause) {
-        return BeanUtilsBean.getInstance().initCause(throwable, cause);
-    }
-
-    /**
-     * Create a cache.
-     * @param <K> the key type of the cache
-     * @param <V> the value type of the cache
-     * @return a new cache
-     * @since 1.8.0
-     */
-    public static <K, V> Map<K, V> createCache() {
-        return new WeakFastHashMap<>();
-    }
-
-    /** An empty class array */
-    static final Class<?>[] EMPTY_CLASS_ARRAY = {};
-
-    /** An empty object array */
-    static final Object[] EMPTY_OBJECT_ARRAY = {};
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java b/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java
index 946459da..ca9ff037 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java
@@ -56,6 +56,58 @@ public class BeanUtilsBean {
                         }
                     };
 
+    /**
+     * Logging for this instance
+     */
+    private static final Log LOG = LogFactory.getLog(BeanUtilsBean.class);
+
+    /** A reference to Throwable's initCause method, or null if it's not there in this JVM */
+    private static final Method INIT_CAUSE_METHOD = getInitCauseMethod();
+
+    /**
+     * Determines the type of a {@code DynaProperty}. Here a special treatment
+     * is needed for mapped properties.
+     *
+     * @param dynaProperty the property descriptor
+     * @param value the value object to be set for this property
+     * @return the type of this property
+     */
+    private static Class<?> dynaPropertyType(final DynaProperty dynaProperty,
+            final Object value) {
+        if (!dynaProperty.isMapped()) {
+            return dynaProperty.getType();
+        }
+        return value == null ? String.class : value.getClass();
+    }
+
+    /**
+     * Returns a <code>Method<code> allowing access to
+     * {@link Throwable#initCause(Throwable)} method of {@link Throwable},
+     * or {@code null} if the method
+     * does not exist.
+     *
+     * @return A {@code Method<code> for <code>Throwable.initCause}, or
+     * {@code null} if unavailable.
+     */
+    private static Method getInitCauseMethod() {
+        try {
+            final Class<?>[] paramsClasses = { Throwable.class };
+            return Throwable.class.getMethod("initCause", paramsClasses);
+        } catch (final NoSuchMethodException e) {
+            final Log log = LogFactory.getLog(BeanUtils.class);
+            if (log.isWarnEnabled()) {
+                log.warn("Throwable does not have initCause() method in JDK 1.3");
+            }
+            return null;
+        } catch (final Throwable e) {
+            final Log log = LogFactory.getLog(BeanUtils.class);
+            if (log.isWarnEnabled()) {
+                log.warn("Error getting the Throwable initCause() method", e);
+            }
+            return null;
+        }
+    }
+
     /**
      * Gets the instance which provides the functionality for {@link BeanUtils}.
      * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
@@ -78,20 +130,12 @@ public class BeanUtilsBean {
         BEANS_BY_CLASSLOADER.set(newInstance);
     }
 
-    /**
-     * Logging for this instance
-     */
-    private static final Log LOG = LogFactory.getLog(BeanUtilsBean.class);
-
     /** Used to perform conversions between object types when setting properties */
     private final ConvertUtilsBean convertUtilsBean;
 
     /** Used to access properties*/
     private final PropertyUtilsBean propertyUtilsBean;
 
-    /** A reference to Throwable's initCause method, or null if it's not there in this JVM */
-    private static final Method INIT_CAUSE_METHOD = getInitCauseMethod();
-
     /**
      * <p>Constructs an instance using new property
      * and conversion instances.</p>
@@ -165,6 +209,42 @@ public class BeanUtilsBean {
         return newBean;
     }
 
+    /**
+     * <p>Converts the value to an object of the specified class (if
+     * possible).</p>
+     *
+     * @param <R> The desired return type
+     * @param value Value to be converted (may be null)
+     * @param type Class of the value to be converted to
+     * @return The converted value
+     *
+     * @throws ConversionException if thrown by an underlying Converter
+     * @since 1.8.0
+     */
+    protected <R> Object convert(final Object value, final Class<R> type) {
+        final Converter<R> converter = getConvertUtils().lookup(type);
+        if (converter != null) {
+            if (LOG.isTraceEnabled()) {
+                LOG.trace("        USING CONVERTER " + converter);
+            }
+            return converter.convert(type, value);
+        }
+        return value;
+    }
+
+    /**
+     * Performs a type conversion of a property value before it is copied to a target
+     * bean. This method delegates to {@link #convert(Object, Class)}, but <b>null</b>
+     * values are not converted. This causes <b>null</b> values to be copied verbatim.
+     *
+     * @param value the value to be converted and copied
+     * @param type the target type of the conversion
+     * @return the converted value
+     */
+    private Object convertForCopy(final Object value, final Class<?> type) {
+        return value != null ? convert(value, type) : value;
+    }
+
     /**
      * <p>Copy property values from the origin bean to the destination bean
      * for all cases where the property names are the same.  For each
@@ -534,6 +614,15 @@ public class BeanUtilsBean {
         return results;
     }
 
+    /**
+     * Gets the {@code ConvertUtilsBean} instance used to perform the conversions.
+     *
+     * @return The ConvertUtils bean instance
+     */
+    public ConvertUtilsBean getConvertUtils() {
+        return convertUtilsBean;
+    }
+
     /**
      * Gets the value of the specified indexed property of the specified
      * bean, as a String.  The zero-relative index of the
@@ -682,6 +771,15 @@ public class BeanUtilsBean {
         return getNestedProperty(bean, name);
     }
 
+    /**
+     * Gets the {@code PropertyUtilsBean} instance used to access properties.
+     *
+     * @return The ConvertUtils bean instance
+     */
+    public PropertyUtilsBean getPropertyUtils() {
+        return propertyUtilsBean;
+    }
+
     /**
      * Gets the value of the specified simple property of the specified
      * bean, converted to a String.
@@ -704,6 +802,26 @@ public class BeanUtilsBean {
         return getConvertUtils().convert(value);
     }
 
+    /**
+     * If we're running on JDK 1.4 or later, initialize the cause for the given throwable.
+     *
+     * @param  throwable The throwable.
+     * @param  cause     The cause of the throwable.
+     * @return  true if the cause was initialized, otherwise false.
+     * @since 1.8.0
+     */
+    public boolean initCause(final Throwable throwable, final Throwable cause) {
+        if (INIT_CAUSE_METHOD != null && cause != null) {
+            try {
+                INIT_CAUSE_METHOD.invoke(throwable, cause);
+                return true;
+            } catch (final Throwable e) {
+             // can't initialize cause
+            }
+        }
+        return false;
+    }
+
     /**
      * <p>Populate the JavaBeans properties of the specified bean, based on
      * the specified name/value pairs.  This method uses Java reflection APIs
@@ -954,122 +1072,4 @@ public class BeanUtilsBean {
                 (e, "Cannot set " + propName);
         }
     }
-
-    /**
-     * Gets the {@code ConvertUtilsBean} instance used to perform the conversions.
-     *
-     * @return The ConvertUtils bean instance
-     */
-    public ConvertUtilsBean getConvertUtils() {
-        return convertUtilsBean;
-    }
-
-    /**
-     * Gets the {@code PropertyUtilsBean} instance used to access properties.
-     *
-     * @return The ConvertUtils bean instance
-     */
-    public PropertyUtilsBean getPropertyUtils() {
-        return propertyUtilsBean;
-    }
-
-    /**
-     * If we're running on JDK 1.4 or later, initialize the cause for the given throwable.
-     *
-     * @param  throwable The throwable.
-     * @param  cause     The cause of the throwable.
-     * @return  true if the cause was initialized, otherwise false.
-     * @since 1.8.0
-     */
-    public boolean initCause(final Throwable throwable, final Throwable cause) {
-        if (INIT_CAUSE_METHOD != null && cause != null) {
-            try {
-                INIT_CAUSE_METHOD.invoke(throwable, cause);
-                return true;
-            } catch (final Throwable e) {
-             // can't initialize cause
-            }
-        }
-        return false;
-    }
-
-    /**
-     * <p>Converts the value to an object of the specified class (if
-     * possible).</p>
-     *
-     * @param <R> The desired return type
-     * @param value Value to be converted (may be null)
-     * @param type Class of the value to be converted to
-     * @return The converted value
-     *
-     * @throws ConversionException if thrown by an underlying Converter
-     * @since 1.8.0
-     */
-    protected <R> Object convert(final Object value, final Class<R> type) {
-        final Converter<R> converter = getConvertUtils().lookup(type);
-        if (converter != null) {
-            if (LOG.isTraceEnabled()) {
-                LOG.trace("        USING CONVERTER " + converter);
-            }
-            return converter.convert(type, value);
-        }
-        return value;
-    }
-
-    /**
-     * Performs a type conversion of a property value before it is copied to a target
-     * bean. This method delegates to {@link #convert(Object, Class)}, but <b>null</b>
-     * values are not converted. This causes <b>null</b> values to be copied verbatim.
-     *
-     * @param value the value to be converted and copied
-     * @param type the target type of the conversion
-     * @return the converted value
-     */
-    private Object convertForCopy(final Object value, final Class<?> type) {
-        return value != null ? convert(value, type) : value;
-    }
-
-    /**
-     * Returns a <code>Method<code> allowing access to
-     * {@link Throwable#initCause(Throwable)} method of {@link Throwable},
-     * or {@code null} if the method
-     * does not exist.
-     *
-     * @return A {@code Method<code> for <code>Throwable.initCause}, or
-     * {@code null} if unavailable.
-     */
-    private static Method getInitCauseMethod() {
-        try {
-            final Class<?>[] paramsClasses = { Throwable.class };
-            return Throwable.class.getMethod("initCause", paramsClasses);
-        } catch (final NoSuchMethodException e) {
-            final Log log = LogFactory.getLog(BeanUtils.class);
-            if (log.isWarnEnabled()) {
-                log.warn("Throwable does not have initCause() method in JDK 1.3");
-            }
-            return null;
-        } catch (final Throwable e) {
-            final Log log = LogFactory.getLog(BeanUtils.class);
-            if (log.isWarnEnabled()) {
-                log.warn("Error getting the Throwable initCause() method", e);
-            }
-            return null;
-        }
-    }
-
-    /**
-     * Determines the type of a {@code DynaProperty}. Here a special treatment
-     * is needed for mapped properties.
-     *
-     * @param dynaProperty the property descriptor
-     * @param value the value object to be set for this property
-     * @return the type of this property
-     */
-    private static Class<?> dynaPropertyType(final DynaProperty dynaProperty,
-            final Object value) {
-        if (!dynaProperty.isMapped()) {
-            return dynaProperty.getType();
-        }
-        return value == null ? String.class : value.getClass();
-    }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java b/src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java
index 22a0864e..1ee53b2f 100644
--- a/src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java
+++ b/src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java
@@ -423,15 +423,6 @@ public class ConstructorUtils {
         return ctor.newInstance(args);
     }
 
-    private static Object[] toArray(final Object arg) {
-        Object[] args = null;
-        if (arg != null) {
-            args = new Object[] { arg };
-        }
-        return args;
-    }
-
-
     /**
      * Delegates to {@link Array#newInstance(Class, int)}.
      *
@@ -446,4 +437,13 @@ public class ConstructorUtils {
         return (T[]) Array.newInstance(componentType, length);
     }
 
+
+    private static Object[] toArray(final Object arg) {
+        Object[] args = null;
+        if (arg != null) {
+            args = new Object[] { arg };
+        }
+        return args;
+    }
+
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/ContextClassLoaderLocal.java b/src/main/java/org/apache/commons/beanutils2/ContextClassLoaderLocal.java
index 9880421d..1ebb3c42 100644
--- a/src/main/java/org/apache/commons/beanutils2/ContextClassLoaderLocal.java
+++ b/src/main/java/org/apache/commons/beanutils2/ContextClassLoaderLocal.java
@@ -112,23 +112,6 @@ public class ContextClassLoaderLocal<T> {
     public ContextClassLoaderLocal() {
     }
 
-    /**
-     * Returns the initial value for this ContextClassLoaderLocal
-     * variable. This method will be called once per Context ClassLoader for
-     * each ContextClassLoaderLocal, the first time it is accessed
-     * with get or set.  If the programmer desires ContextClassLoaderLocal variables
-     * to be initialized to some value other than null, ContextClassLoaderLocal must
-     * be subclassed, and this method overridden.  Typically, an anonymous
-     * inner class will be used.  Typical implementations of initialValue
-     * will call an appropriate constructor and return the newly constructed
-     * object.
-     *
-     * @return a new Object to be used as an initial value for this ContextClassLoaderLocal
-     */
-    protected T initialValue() {
-        return null;
-    }
-
     /**
      * Gets the instance which provides the functionality for {@link BeanUtils}.
      * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
@@ -166,6 +149,23 @@ public class ContextClassLoaderLocal<T> {
         return globalValue;
     }
 
+    /**
+     * Returns the initial value for this ContextClassLoaderLocal
+     * variable. This method will be called once per Context ClassLoader for
+     * each ContextClassLoaderLocal, the first time it is accessed
+     * with get or set.  If the programmer desires ContextClassLoaderLocal variables
+     * to be initialized to some value other than null, ContextClassLoaderLocal must
+     * be subclassed, and this method overridden.  Typically, an anonymous
+     * inner class will be used.  Typical implementations of initialValue
+     * will call an appropriate constructor and return the newly constructed
+     * object.
+     *
+     * @return a new Object to be used as an initial value for this ContextClassLoaderLocal
+     */
+    protected T initialValue() {
+        return null;
+    }
+
     /**
      * Sets the value - a value is provided per (thread) context classloader.
      * This mechanism provides isolation for web apps deployed in the same container.
diff --git a/src/main/java/org/apache/commons/beanutils2/ConversionException.java b/src/main/java/org/apache/commons/beanutils2/ConversionException.java
index 707ab98f..fc061c97 100644
--- a/src/main/java/org/apache/commons/beanutils2/ConversionException.java
+++ b/src/main/java/org/apache/commons/beanutils2/ConversionException.java
@@ -25,6 +25,8 @@ package org.apache.commons.beanutils2;
  */
 public class ConversionException extends RuntimeException {
 
+    private static final long serialVersionUID = 1L;
+
     /**
      * Constructs a new instance with a message formatted using {@link String#format(String, Object...)}.
      *
@@ -37,8 +39,6 @@ public class ConversionException extends RuntimeException {
         return new ConversionException(String.format(format, args));
     }
 
-    private static final long serialVersionUID = 1L;
-
     /**
      * Constructs a new exception with the specified message.
      *
diff --git a/src/main/java/org/apache/commons/beanutils2/ConvertUtils.java b/src/main/java/org/apache/commons/beanutils2/ConvertUtils.java
index de03d847..bf2ae597 100644
--- a/src/main/java/org/apache/commons/beanutils2/ConvertUtils.java
+++ b/src/main/java/org/apache/commons/beanutils2/ConvertUtils.java
@@ -43,6 +43,20 @@ public class ConvertUtils {
         return ConvertUtilsBean.getInstance().convert(value);
     }
 
+    /**
+     * <p>Converts the value to an object of the specified class (if
+     * possible).</p>
+     *
+     * @param value Value to be converted (may be null)
+     * @param targetType Class of the value to be converted to (must not be null)
+     * @return The converted value
+     *
+     * @throws ConversionException if thrown by an underlying Converter
+     */
+    public static Object convert(final Object value, final Class<?> targetType) {
+        return ConvertUtilsBean.getInstance().convert(value, targetType);
+    }
+
     /**
      * <p>Converts the specified value to an object of the specified class (if
      * possible).  Otherwise, return a String representation of the value.</p>
@@ -75,20 +89,6 @@ public class ConvertUtils {
         return ConvertUtilsBean.getInstance().convert(values, clazz);
     }
 
-    /**
-     * <p>Converts the value to an object of the specified class (if
-     * possible).</p>
-     *
-     * @param value Value to be converted (may be null)
-     * @param targetType Class of the value to be converted to (must not be null)
-     * @return The converted value
-     *
-     * @throws ConversionException if thrown by an underlying Converter
-     */
-    public static Object convert(final Object value, final Class<?> targetType) {
-        return ConvertUtilsBean.getInstance().convert(value, targetType);
-    }
-
     /**
      * <p>Remove all registered {@link Converter}s, and re-establish the
      * standard Converters.</p>
@@ -114,22 +114,6 @@ public class ConvertUtils {
         ConvertUtilsBean.getInstance().deregister(clazz);
     }
 
-    /**
-     * <p>Look up and return any registered {@link Converter} for the specified
-     * destination class; if there is no registered Converter, return
-     * {@code null}.</p>
-     *
-     * <p>For more details see {@code ConvertUtilsBean}.</p>
-     *
-     * @param <T> The converter type.
-     * @param clazz Class for which to return a registered Converter
-     * @return The registered {@link Converter} or {@code null} if not found
-     * @see ConvertUtilsBean#lookup(Class)
-     */
-    public static <T> Converter<T> lookup(final Class<T> clazz) {
-        return ConvertUtilsBean.getInstance().lookup(clazz);
-    }
-
     /**
      * Look up and return any registered {@link Converter} for the specified
      * source and destination class; if there is no registered Converter,
@@ -145,19 +129,19 @@ public class ConvertUtils {
     }
 
     /**
-     * <p>Register a custom {@link Converter} for the specified destination
-     * {@code Class}, replacing any previously registered Converter.</p>
+     * <p>Look up and return any registered {@link Converter} for the specified
+     * destination class; if there is no registered Converter, return
+     * {@code null}.</p>
      *
      * <p>For more details see {@code ConvertUtilsBean}.</p>
      *
      * @param <T> The converter type.
-     * @param converter Converter to be registered
-     * @param clazz Destination class for conversions performed by this
-     *  Converter
-     * @see ConvertUtilsBean#register(Converter, Class)
+     * @param clazz Class for which to return a registered Converter
+     * @return The registered {@link Converter} or {@code null} if not found
+     * @see ConvertUtilsBean#lookup(Class)
      */
-    public static <T> void register(final Converter<T> converter, final Class<T> clazz) {
-        ConvertUtilsBean.getInstance().register(converter, clazz);
+    public static <T> Converter<T> lookup(final Class<T> clazz) {
+        return ConvertUtilsBean.getInstance().lookup(clazz);
     }
 
     /**
@@ -204,4 +188,20 @@ public class ConvertUtils {
         }
         return type;
     }
+
+    /**
+     * <p>Register a custom {@link Converter} for the specified destination
+     * {@code Class}, replacing any previously registered Converter.</p>
+     *
+     * <p>For more details see {@code ConvertUtilsBean}.</p>
+     *
+     * @param <T> The converter type.
+     * @param converter Converter to be registered
+     * @param clazz Destination class for conversions performed by this
+     *  Converter
+     * @see ConvertUtilsBean#register(Converter, Class)
+     */
+    public static <T> void register(final Converter<T> converter, final Class<T> clazz) {
+        ConvertUtilsBean.getInstance().register(converter, clazz);
+    }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java b/src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java
index 46c6093c..dc9ddaa0 100644
--- a/src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java
@@ -174,6 +174,11 @@ public class ConvertUtilsBean {
     private static final Integer ZERO = Integer.valueOf(0);
     private static final Character SPACE = Character.valueOf(' ');
 
+    /**
+     * The {@code Log} instance for this class.
+     */
+    private static final Log LOG = LogFactory.getLog(ConvertUtilsBean.class);
+
     /**
      * Get singleton instance
      * @return The singleton instance
@@ -188,11 +193,6 @@ public class ConvertUtilsBean {
      */
     private final WeakFastHashMap<Class<?>, Converter<?>> converters = new WeakFastHashMap<>();
 
-    /**
-     * The {@code Log} instance for this class.
-     */
-    private static final Log LOG = LogFactory.getLog(ConvertUtilsBean.class);
-
     /** Constructs a bean with standard converters registered */
     public ConvertUtilsBean() {
         converters.setFast(false);
@@ -232,6 +232,61 @@ public class ConvertUtilsBean {
 
     }
 
+    /**
+     * Convert the value to an object of the specified class (if
+     * possible). If no converter for the desired target type is registered,
+     * the passed in object is returned unchanged.
+     *
+     * @param <T> The Class type.
+     * @param value Value to be converted (may be null)
+     * @param targetType Class of the value to be converted to (must not be null)
+     * @return The converted value
+     *
+     * @throws ConversionException if thrown by an underlying Converter
+     */
+    public <T> Object convert(final Object value, final Class<T> targetType) {
+        final boolean nullValue = value == null;
+        final Class<?> sourceType = nullValue ? null : value.getClass();
+
+        if (LOG.isDebugEnabled()) {
+            if (nullValue) {
+                LOG.debug("Convert null value to type '" + targetType.getName() + "'");
+            } else {
+                LOG.debug("Convert type '" + sourceType.getName() + "' value '" + value + "' to type '"
+                    + targetType.getName() + "'");
+            }
+        }
+
+        Object converted = value;
+        final Converter<T> converter = lookup(sourceType, targetType);
+        if (converter != null) {
+            if (LOG.isTraceEnabled()) {
+                LOG.trace("  Using converter " + converter);
+            }
+            converted = converter.convert(targetType, value);
+        }
+        if (String.class.equals(targetType) && converted != null && !(converted instanceof String)) {
+
+            // NOTE: For backwards compatibility, if the Converter
+            // doesn't handle conversion-->String then
+            // use the registered String Converter
+            final Converter<String> strConverter = lookup(String.class);
+            if (strConverter != null) {
+                if (LOG.isTraceEnabled()) {
+                    LOG.trace("  Using converter " + converter);
+                }
+                converted = strConverter.convert(String.class, converted);
+            }
+
+            // If the object still isn't a String, use toString() method
+            if (converted != null && !(converted instanceof String)) {
+                converted = converted.toString();
+            }
+
+        }
+        return converted;
+    }
+
     /**
      * Convert the specified value to an object of the specified class (if
      * possible). Otherwise, return a {@link String} representation of the value.
@@ -299,74 +354,91 @@ public class ConvertUtilsBean {
     }
 
     /**
-     * Convert the value to an object of the specified class (if
-     * possible). If no converter for the desired target type is registered,
-     * the passed in object is returned unchanged.
+     * Remove all registered {@link Converter}s, and re-establish the
+     * standard Converters.
+     */
+    public void deregister() {
+
+        converters.clear();
+
+        registerPrimitives(false);
+        registerStandard(false, false);
+        registerOther(true);
+        registerArrays(false, 0);
+        register(BigDecimal.class, new BigDecimalConverter());
+        register(BigInteger.class, new BigIntegerConverter());
+    }
+
+    /**
+     * Remove any registered {@link Converter} for the specified destination
+     * {@code Class}.
      *
-     * @param <T> The Class type.
-     * @param value Value to be converted (may be null)
-     * @param targetType Class of the value to be converted to (must not be null)
-     * @return The converted value
+     * @param clazz Class for which to remove a registered Converter
+     */
+    public void deregister(final Class<?> clazz) {
+        converters.remove(clazz);
+    }
+
+    /**
+     * Look up and return any registered {@link Converter} for the specified
+     * source and destination class; if there is no registered Converter,
+     * return {@code null}.
      *
-     * @throws ConversionException if thrown by an underlying Converter
+     * @param <T> The converter type.
+     * @param sourceType Class of the value being converted
+     * @param targetType Class of the value to be converted to
+     * @return The registered {@link Converter} or {@code null} if not found
      */
-    public <T> Object convert(final Object value, final Class<T> targetType) {
-        final boolean nullValue = value == null;
-        final Class<?> sourceType = nullValue ? null : value.getClass();
+    public <T> Converter<T> lookup(final Class<?> sourceType, final Class<T> targetType) {
 
-        if (LOG.isDebugEnabled()) {
-            if (nullValue) {
-                LOG.debug("Convert null value to type '" + targetType.getName() + "'");
-            } else {
-                LOG.debug("Convert type '" + sourceType.getName() + "' value '" + value + "' to type '"
-                    + targetType.getName() + "'");
-            }
+        if (targetType == null) {
+            throw new IllegalArgumentException("Target type is missing");
+        }
+        if (sourceType == null) {
+            return lookup(targetType);
         }
 
-        Object converted = value;
-        final Converter<T> converter = lookup(sourceType, targetType);
-        if (converter != null) {
-            if (LOG.isTraceEnabled()) {
-                LOG.trace("  Using converter " + converter);
+        Converter converter = null;
+        // Convert --> String
+        if (targetType == String.class) {
+            converter = lookup(sourceType);
+            if (converter == null && (sourceType.isArray() ||
+                        Collection.class.isAssignableFrom(sourceType))) {
+                converter = lookup(String[].class);
             }
-            converted = converter.convert(targetType, value);
+            if (converter == null) {
+                converter = lookup(String.class);
+            }
+            return converter;
         }
-        if (String.class.equals(targetType) && converted != null && !(converted instanceof String)) {
 
-            // NOTE: For backwards compatibility, if the Converter
-            // doesn't handle conversion-->String then
-            // use the registered String Converter
-            final Converter<String> strConverter = lookup(String.class);
-            if (strConverter != null) {
-                if (LOG.isTraceEnabled()) {
-                    LOG.trace("  Using converter " + converter);
-                }
-                converted = strConverter.convert(String.class, converted);
+        // Convert --> String array
+        if (targetType == String[].class) {
+            if (sourceType.isArray() || Collection.class.isAssignableFrom(sourceType)) {
+                converter = lookup(sourceType);
             }
-
-            // If the object still isn't a String, use toString() method
-            if (converted != null && !(converted instanceof String)) {
-                converted = converted.toString();
+            if (converter == null) {
+                converter = lookup(String[].class);
             }
-
+            return converter;
         }
-        return converted;
+
+        return lookup(targetType);
+
     }
 
     /**
-     * Remove all registered {@link Converter}s, and re-establish the
-     * standard Converters.
+     * Look up and return any registered {@link Converter} for the specified
+     * destination class; if there is no registered Converter, return
+     * {@code null}.
+     *
+     * @param <T> The converter type.
+     * @param clazz Class for which to return a registered Converter
+     * @return The registered {@link Converter} or {@code null} if not found
      */
-    public void deregister() {
-
-        converters.clear();
-
-        registerPrimitives(false);
-        registerStandard(false, false);
-        registerOther(true);
-        registerArrays(false, 0);
-        register(BigDecimal.class, new BigDecimalConverter());
-        register(BigInteger.class, new BigIntegerConverter());
+    @SuppressWarnings("unchecked")
+    public <T> Converter<T> lookup(final Class<T> clazz) {
+        return (Converter<T>) converters.get(clazz);
     }
 
     /**
@@ -391,81 +463,109 @@ public class ConvertUtilsBean {
         registerArrays(throwException, defaultArraySize);
     }
 
+    /** strictly for convenience since it has same parameter order as Map.put */
+    private <T> void register(final Class<?> clazz, final Converter<T> converter) {
+        register(new ConverterFacade<>(converter), clazz);
+    }
+
     /**
-     * Register the converters for primitive types.
-     * </p>
-     * This method registers the following converters:
-     * <ul>
-     *     <li>{@code Boolean.TYPE} - {@link BooleanConverter}</li>
-     *     <li>{@code Byte.TYPE} - {@link ByteConverter}</li>
-     *     <li>{@code Character.TYPE} - {@link CharacterConverter}</li>
-     *     <li>{@code Double.TYPE} - {@link DoubleConverter}</li>
-     *     <li>{@code Float.TYPE} - {@link FloatConverter}</li>
-     *     <li>{@code Integer.TYPE} - {@link IntegerConverter}</li>
-     *     <li>{@code Long.TYPE} - {@link LongConverter}</li>
-     *     <li>{@code Short.TYPE} - {@link ShortConverter}</li>
-     * </ul>
-     * @param throwException {@code true} if the converters should
-     * throw an exception when a conversion error occurs, otherwise <code>
-     * {@code false} if a default value should be used.
+     * Register a custom {@link Converter} for the specified destination
+     * {@code Class}, replacing any previously registered Converter.
+     *
+     * @param converter Converter to be registered
+     * @param clazz Destination class for conversions performed by this
+     *  Converter
      */
-    private void registerPrimitives(final boolean throwException) {
-        register(Boolean.TYPE,   throwException ? new BooleanConverter()    : new BooleanConverter(Boolean.FALSE));
-        register(Byte.TYPE,      throwException ? new ByteConverter()       : new ByteConverter(ZERO));
-        register(Character.TYPE, throwException ? new CharacterConverter()  : new CharacterConverter(SPACE));
-        register(Double.TYPE,    throwException ? new DoubleConverter()     : new DoubleConverter(ZERO));
-        register(Float.TYPE,     throwException ? new FloatConverter()      : new FloatConverter(ZERO));
-        register(Integer.TYPE,   throwException ? new IntegerConverter()    : new IntegerConverter(ZERO));
-        register(Long.TYPE,      throwException ? new LongConverter()       : new LongConverter(ZERO));
-        register(Short.TYPE,     throwException ? new ShortConverter()      : new ShortConverter(ZERO));
+    public void register(final Converter converter, final Class<?> clazz) {
+        converters.put(clazz, converter);
     }
 
     /**
-     * Register the converters for standard types.
-     * </p>
-     * This method registers the following converters:
-     * <ul>
-     *     <li>{@code BigDecimal.class} - {@link BigDecimalConverter}</li>
-     *     <li>{@code BigInteger.class} - {@link BigIntegerConverter}</li>
-     *     <li>{@code Boolean.class} - {@link BooleanConverter}</li>
-     *     <li>{@code Byte.class} - {@link ByteConverter}</li>
-     *     <li>{@code Character.class} - {@link CharacterConverter}</li>
-     *     <li>{@code Double.class} - {@link DoubleConverter}</li>
-     *     <li>{@code Float.class} - {@link FloatConverter}</li>
-     *     <li>{@code Integer.class} - {@link IntegerConverter}</li>
-     *     <li>{@code Long.class} - {@link LongConverter}</li>
-     *     <li>{@code Short.class} - {@link ShortConverter}</li>
-     *     <li>{@code String.class} - {@link StringConverter}</li>
-     * </ul>
-     * @param throwException {@code true} if the converters should
-     * throw an exception when a conversion error occurs, otherwise <code>
-     * {@code false} if a default value should be used.
-     * @param defaultNull {@code true}if the <i>standard</i> converters
-     * (see {@link ConvertUtilsBean#registerStandard(boolean, boolean)})
-     * should use a default value of {@code null</code>, otherwise <code>false}.
-     * N.B. This values is ignored if {@code throwException</code> is <code>true}
+     * Register a new ArrayConverter with the specified element delegate converter
+     * that returns a default array of the specified size in the event of conversion errors.
+     *
+     * @param componentType The component type of the array
+     * @param componentConverter The converter to delegate to for the array elements
+     * @param throwException Whether a conversion exception should be thrown or a default
+     * value used in the event of a conversion error
+     * @param defaultArraySize The size of the default array
      */
-    private void registerStandard(final boolean throwException, final boolean defaultNull) {
+    private <T> void registerArrayConverter(final Class<T> componentType, final Converter<T> componentConverter,
+            final boolean throwException, final int defaultArraySize) {
+        final Class<T[]> arrayType = (Class<T[]>) Array.newInstance(componentType, 0).getClass();
+        final Converter<T[]> arrayConverter;
+        if (throwException) {
+            arrayConverter = new ArrayConverter<>(arrayType, componentConverter);
+        } else {
+            arrayConverter = new ArrayConverter<>(arrayType, componentConverter, defaultArraySize);
+        }
+        register(arrayType, arrayConverter);
+    }
 
-        final Number     defaultNumber     = defaultNull ? null : ZERO;
-        final BigDecimal bigDecDeflt       = defaultNull ? null : new BigDecimal("0.0");
-        final BigInteger bigIntDeflt       = defaultNull ? null : new BigInteger("0");
-        final Boolean    booleanDefault    = defaultNull ? null : Boolean.FALSE;
-        final Character  charDefault       = defaultNull ? null : SPACE;
-        final String     stringDefault     = defaultNull ? null : "";
+    /**
+     * Register array converters.
+     *
+     * @param throwException {@code true} if the converters should
+     * throw an exception when a conversion error occurs, otherwise <code>
+     * {@code false} if a default value should be used.
+     * @param defaultArraySize The size of the default array value for array converters
+     * (N.B. This values is ignored if {@code throwException</code> is <code>true}).
+     * Specifying a value less than zero causes a <code>null<code> value to be used for
+     * the default.
+     */
+    private void registerArrays(final boolean throwException, final int defaultArraySize) {
+        // @formatter:off
 
-        register(BigDecimal.class, throwException ? new BigDecimalConverter() : new BigDecimalConverter(bigDecDeflt));
-        register(BigInteger.class, throwException ? new BigIntegerConverter() : new BigIntegerConverter(bigIntDeflt));
-        register(Boolean.class,    throwException ? new BooleanConverter()    : new BooleanConverter(booleanDefault));
-        register(Byte.class,       throwException ? new ByteConverter()       : new ByteConverter(defaultNumber));
-        register(Character.class,  throwException ? new CharacterConverter()  : new CharacterConverter(charDefault));
-        register(Double.class,     throwException ? new DoubleConverter()     : new DoubleConverter(defaultNumber));
-        register(Float.class,      throwException ? new FloatConverter()      : new FloatConverter(defaultNumber));
-        register(Integer.class,    throwException ? new IntegerConverter()    : new IntegerConverter(defaultNumber));
-        register(Long.class,       throwException ? new LongConverter()       : new LongConverter(defaultNumber));
-        register(Short.class,      throwException ? new ShortConverter()      : new ShortConverter(defaultNumber));
-        register(String.class,     throwException ? new StringConverter()     : new StringConverter(stringDefault));
+        // Primitives
+        registerArrayConverter(Boolean.TYPE,   new BooleanConverter(),   throwException, defaultArraySize);
+        registerArrayConverter(Byte.TYPE,      new ByteConverter(),      throwException, defaultArraySize);
+        registerArrayConverter(Character.TYPE, new CharacterConverter(), throwException, defaultArraySize);
+        registerArrayConverter(Double.TYPE,    new DoubleConverter(),    throwException, defaultArraySize);
+        registerArrayConverter(Float.TYPE,     new FloatConverter(),     throwException, defaultArraySize);
+        registerArrayConverter(Integer.TYPE,   new IntegerConverter(),   throwException, defaultArraySize);
+        registerArrayConverter(Long.TYPE,      new LongConverter(),      throwException, defaultArraySize);
+        registerArrayConverter(Short.TYPE,     new ShortConverter(),     throwException, defaultArraySize);
 
+        // Standard
+        registerArrayConverter(BigDecimal.class, new BigDecimalConverter(), throwException, defaultArraySize);
+        registerArrayConverter(BigInteger.class, new BigIntegerConverter(), throwException, defaultArraySize);
+        registerArrayConverter(Boolean.class,    new BooleanConverter(),    throwException, defaultArraySize);
+        registerArrayConverter(Byte.class,       new ByteConverter(),       throwException, defaultArraySize);
+        registerArrayConverter(Character.class,  new CharacterConverter(),  throwException, defaultArraySize);
+        registerArrayConverter(Double.class,     new DoubleConverter(),     throwException, defaultArraySize);
+        registerArrayConverter(Float.class,      new FloatConverter(),      throwException, defaultArraySize);
+        registerArrayConverter(Integer.class,    new IntegerConverter(),    throwException, defaultArraySize);
+        registerArrayConverter(Long.class,       new LongConverter(),       throwException, defaultArraySize);
+        registerArrayConverter(Short.class,      new ShortConverter(),      throwException, defaultArraySize);
+        registerArrayConverter(String.class,     new StringConverter(),     throwException, defaultArraySize);
+
+        // Other
+        registerArrayConverter(Class.class,          new ClassConverter(),         throwException, defaultArraySize);
+        registerArrayConverter(Enum.class,           new EnumConverter(),          throwException, defaultArraySize);
+        registerArrayConverter(java.util.Date.class, new DateConverter(),          throwException, defaultArraySize);
+        registerArrayConverter(Calendar.class,       new CalendarConverter(),      throwException, defaultArraySize);
+        registerArrayConverter(File.class,           new FileConverter(),          throwException, defaultArraySize);
+        registerArrayConverter(Path.class,           new PathConverter(),          throwException, defaultArraySize);
+        registerArrayConverter(java.sql.Date.class,  new SqlDateConverter(),       throwException, defaultArraySize);
+        registerArrayConverter(java.sql.Time.class,  new SqlTimeConverter(),       throwException, defaultArraySize);
+        registerArrayConverter(Timestamp.class,      new SqlTimestampConverter(),  throwException, defaultArraySize);
+        registerArrayConverter(URL.class,            new URLConverter(),           throwException, defaultArraySize);
+        registerArrayConverter(URI.class,            new URIConverter(),           throwException, defaultArraySize);
+        registerArrayConverter(UUID.class,           new UUIDConverter(),          throwException, defaultArraySize);
+        registerArrayConverter(LocalDate.class,      new LocalDateConverter(),     throwException, defaultArraySize);
+        registerArrayConverter(LocalDateTime.class,  new LocalDateTimeConverter(), throwException, defaultArraySize);
+        registerArrayConverter(LocalTime.class,      new LocalTimeConverter(),     throwException, defaultArraySize);
+        registerArrayConverter(OffsetDateTime.class, new OffsetDateTimeConverter(),throwException, defaultArraySize);
+        registerArrayConverter(OffsetTime.class,     new OffsetTimeConverter(),    throwException, defaultArraySize);
+        registerArrayConverter(ZonedDateTime.class,  new ZonedDateTimeConverter(), throwException, defaultArraySize);
+        registerArrayConverter(Duration.class,       new DurationConverter(),      throwException, defaultArraySize);
+        registerArrayConverter(MonthDay.class,       new MonthDayConverter(),      throwException, defaultArraySize);
+        registerArrayConverter(Period.class,         new PeriodConverter(),        throwException, defaultArraySize);
+        registerArrayConverter(Year.class,           new YearConverter(),          throwException, defaultArraySize);
+        registerArrayConverter(YearMonth.class,      new YearMonthConverter(),     throwException, defaultArraySize);
+        registerArrayConverter(ZoneId.class,         new ZoneIdConverter(),        throwException, defaultArraySize);
+        registerArrayConverter(ZoneOffset.class,     new ZoneOffsetConverter(),    throwException, defaultArraySize);
+        // @formatter:on
     }
 
     /**
@@ -534,179 +634,79 @@ public class ConvertUtilsBean {
     }
 
     /**
-     * Register array converters.
-     *
+     * Register the converters for primitive types.
+     * </p>
+     * This method registers the following converters:
+     * <ul>
+     *     <li>{@code Boolean.TYPE} - {@link BooleanConverter}</li>
+     *     <li>{@code Byte.TYPE} - {@link ByteConverter}</li>
+     *     <li>{@code Character.TYPE} - {@link CharacterConverter}</li>
+     *     <li>{@code Double.TYPE} - {@link DoubleConverter}</li>
+     *     <li>{@code Float.TYPE} - {@link FloatConverter}</li>
+     *     <li>{@code Integer.TYPE} - {@link IntegerConverter}</li>
+     *     <li>{@code Long.TYPE} - {@link LongConverter}</li>
+     *     <li>{@code Short.TYPE} - {@link ShortConverter}</li>
+     * </ul>
      * @param throwException {@code true} if the converters should
      * throw an exception when a conversion error occurs, otherwise <code>
      * {@code false} if a default value should be used.
-     * @param defaultArraySize The size of the default array value for array converters
-     * (N.B. This values is ignored if {@code throwException</code> is <code>true}).
-     * Specifying a value less than zero causes a <code>null<code> value to be used for
-     * the default.
-     */
-    private void registerArrays(final boolean throwException, final int defaultArraySize) {
-        // @formatter:off
-
-        // Primitives
-        registerArrayConverter(Boolean.TYPE,   new BooleanConverter(),   throwException, defaultArraySize);
-        registerArrayConverter(Byte.TYPE,      new ByteConverter(),      throwException, defaultArraySize);
-        registerArrayConverter(Character.TYPE, new CharacterConverter(), throwException, defaultArraySize);
-        registerArrayConverter(Double.TYPE,    new DoubleConverter(),    throwException, defaultArraySize);
-        registerArrayConverter(Float.TYPE,     new FloatConverter(),     throwException, defaultArraySize);
-        registerArrayConverter(Integer.TYPE,   new IntegerConverter(),   throwException, defaultArraySize);
-        registerArrayConverter(Long.TYPE,      new LongConverter(),      throwException, defaultArraySize);
-        registerArrayConverter(Short.TYPE,     new ShortConverter(),     throwException, defaultArraySize);
-
-        // Standard
-        registerArrayConverter(BigDecimal.class, new BigDecimalConverter(), throwException, defaultArraySize);
-        registerArrayConverter(BigInteger.class, new BigIntegerConverter(), throwException, defaultArraySize);
-        registerArrayConverter(Boolean.class,    new BooleanConverter(),    throwException, defaultArraySize);
-        registerArrayConverter(Byte.class,       new ByteConverter(),       throwException, defaultArraySize);
-        registerArrayConverter(Character.class,  new CharacterConverter(),  throwException, defaultArraySize);
-        registerArrayConverter(Double.class,     new DoubleConverter(),     throwException, defaultArraySize);
-        registerArrayConverter(Float.class,      new FloatConverter(),      throwException, defaultArraySize);
-        registerArrayConverter(Integer.class,    new IntegerConverter(),    throwException, defaultArraySize);
-        registerArrayConverter(Long.class,       new LongConverter(),       throwException, defaultArraySize);
-        registerArrayConverter(Short.class,      new ShortConverter(),      throwException, defaultArraySize);
-        registerArrayConverter(String.class,     new StringConverter(),     throwException, defaultArraySize);
-
-        // Other
-        registerArrayConverter(Class.class,          new ClassConverter(),         throwException, defaultArraySize);
-        registerArrayConverter(Enum.class,           new EnumConverter(),          throwException, defaultArraySize);
-        registerArrayConverter(java.util.Date.class, new DateConverter(),          throwException, defaultArraySize);
-        registerArrayConverter(Calendar.class,       new CalendarConverter(),      throwException, defaultArraySize);
-        registerArrayConverter(File.class,           new FileConverter(),          throwException, defaultArraySize);
-        registerArrayConverter(Path.class,           new PathConverter(),          throwException, defaultArraySize);
-        registerArrayConverter(java.sql.Date.class,  new SqlDateConverter(),       throwException, defaultArraySize);
-        registerArrayConverter(java.sql.Time.class,  new SqlTimeConverter(),       throwException, defaultArraySize);
-        registerArrayConverter(Timestamp.class,      new SqlTimestampConverter(),  throwException, defaultArraySize);
-        registerArrayConverter(URL.class,            new URLConverter(),           throwException, defaultArraySize);
-        registerArrayConverter(URI.class,            new URIConverter(),           throwException, defaultArraySize);
-        registerArrayConverter(UUID.class,           new UUIDConverter(),          throwException, defaultArraySize);
-        registerArrayConverter(LocalDate.class,      new LocalDateConverter(),     throwException, defaultArraySize);
-        registerArrayConverter(LocalDateTime.class,  new LocalDateTimeConverter(), throwException, defaultArraySize);
-        registerArrayConverter(LocalTime.class,      new LocalTimeConverter(),     throwException, defaultArraySize);
-        registerArrayConverter(OffsetDateTime.class, new OffsetDateTimeConverter(),throwException, defaultArraySize);
-        registerArrayConverter(OffsetTime.class,     new OffsetTimeConverter(),    throwException, defaultArraySize);
-        registerArrayConverter(ZonedDateTime.class,  new ZonedDateTimeConverter(), throwException, defaultArraySize);
-        registerArrayConverter(Duration.class,       new DurationConverter(),      throwException, defaultArraySize);
-        registerArrayConverter(MonthDay.class,       new MonthDayConverter(),      throwException, defaultArraySize);
-        registerArrayConverter(Period.class,         new PeriodConverter(),        throwException, defaultArraySize);
-        registerArrayConverter(Year.class,           new YearConverter(),          throwException, defaultArraySize);
-        registerArrayConverter(YearMonth.class,      new YearMonthConverter(),     throwException, defaultArraySize);
-        registerArrayConverter(ZoneId.class,         new ZoneIdConverter(),        throwException, defaultArraySize);
-        registerArrayConverter(ZoneOffset.class,     new ZoneOffsetConverter(),    throwException, defaultArraySize);
-        // @formatter:on
-    }
-
-    /**
-     * Register a new ArrayConverter with the specified element delegate converter
-     * that returns a default array of the specified size in the event of conversion errors.
-     *
-     * @param componentType The component type of the array
-     * @param componentConverter The converter to delegate to for the array elements
-     * @param throwException Whether a conversion exception should be thrown or a default
-     * value used in the event of a conversion error
-     * @param defaultArraySize The size of the default array
-     */
-    private <T> void registerArrayConverter(final Class<T> componentType, final Converter<T> componentConverter,
-            final boolean throwException, final int defaultArraySize) {
-        final Class<T[]> arrayType = (Class<T[]>) Array.newInstance(componentType, 0).getClass();
-        final Converter<T[]> arrayConverter;
-        if (throwException) {
-            arrayConverter = new ArrayConverter<>(arrayType, componentConverter);
-        } else {
-            arrayConverter = new ArrayConverter<>(arrayType, componentConverter, defaultArraySize);
-        }
-        register(arrayType, arrayConverter);
-    }
-
-    /** strictly for convenience since it has same parameter order as Map.put */
-    private <T> void register(final Class<?> clazz, final Converter<T> converter) {
-        register(new ConverterFacade<>(converter), clazz);
-    }
-
-    /**
-     * Remove any registered {@link Converter} for the specified destination
-     * {@code Class}.
-     *
-     * @param clazz Class for which to remove a registered Converter
      */
-    public void deregister(final Class<?> clazz) {
-        converters.remove(clazz);
-    }
-
-    /**
-     * Look up and return any registered {@link Converter} for the specified
-     * destination class; if there is no registered Converter, return
-     * {@code null}.
-     *
-     * @param <T> The converter type.
-     * @param clazz Class for which to return a registered Converter
-     * @return The registered {@link Converter} or {@code null} if not found
-     */
-    @SuppressWarnings("unchecked")
-    public <T> Converter<T> lookup(final Class<T> clazz) {
-        return (Converter<T>) converters.get(clazz);
+    private void registerPrimitives(final boolean throwException) {
+        register(Boolean.TYPE,   throwException ? new BooleanConverter()    : new BooleanConverter(Boolean.FALSE));
+        register(Byte.TYPE,      throwException ? new ByteConverter()       : new ByteConverter(ZERO));
+        register(Character.TYPE, throwException ? new CharacterConverter()  : new CharacterConverter(SPACE));
+        register(Double.TYPE,    throwException ? new DoubleConverter()     : new DoubleConverter(ZERO));
+        register(Float.TYPE,     throwException ? new FloatConverter()      : new FloatConverter(ZERO));
+        register(Integer.TYPE,   throwException ? new IntegerConverter()    : new IntegerConverter(ZERO));
+        register(Long.TYPE,      throwException ? new LongConverter()       : new LongConverter(ZERO));
+        register(Short.TYPE,     throwException ? new ShortConverter()      : new ShortConverter(ZERO));
     }
 
     /**
-     * Look up and return any registered {@link Converter} for the specified
-     * source and destination class; if there is no registered Converter,
-     * return {@code null}.
-     *
-     * @param <T> The converter type.
-     * @param sourceType Class of the value being converted
-     * @param targetType Class of the value to be converted to
-     * @return The registered {@link Converter} or {@code null} if not found
+     * Register the converters for standard types.
+     * </p>
+     * This method registers the following converters:
+     * <ul>
+     *     <li>{@code BigDecimal.class} - {@link BigDecimalConverter}</li>
+     *     <li>{@code BigInteger.class} - {@link BigIntegerConverter}</li>
+     *     <li>{@code Boolean.class} - {@link BooleanConverter}</li>
+     *     <li>{@code Byte.class} - {@link ByteConverter}</li>
+     *     <li>{@code Character.class} - {@link CharacterConverter}</li>
+     *     <li>{@code Double.class} - {@link DoubleConverter}</li>
+     *     <li>{@code Float.class} - {@link FloatConverter}</li>
+     *     <li>{@code Integer.class} - {@link IntegerConverter}</li>
+     *     <li>{@code Long.class} - {@link LongConverter}</li>
+     *     <li>{@code Short.class} - {@link ShortConverter}</li>
+     *     <li>{@code String.class} - {@link StringConverter}</li>
+     * </ul>
+     * @param throwException {@code true} if the converters should
+     * throw an exception when a conversion error occurs, otherwise <code>
+     * {@code false} if a default value should be used.
+     * @param defaultNull {@code true}if the <i>standard</i> converters
+     * (see {@link ConvertUtilsBean#registerStandard(boolean, boolean)})
+     * should use a default value of {@code null</code>, otherwise <code>false}.
+     * N.B. This values is ignored if {@code throwException</code> is <code>true}
      */
-    public <T> Converter<T> lookup(final Class<?> sourceType, final Class<T> targetType) {
-
-        if (targetType == null) {
-            throw new IllegalArgumentException("Target type is missing");
-        }
-        if (sourceType == null) {
-            return lookup(targetType);
-        }
-
-        Converter converter = null;
-        // Convert --> String
-        if (targetType == String.class) {
-            converter = lookup(sourceType);
-            if (converter == null && (sourceType.isArray() ||
-                        Collection.class.isAssignableFrom(sourceType))) {
-                converter = lookup(String[].class);
-            }
-            if (converter == null) {
-                converter = lookup(String.class);
-            }
-            return converter;
-        }
-
-        // Convert --> String array
-        if (targetType == String[].class) {
-            if (sourceType.isArray() || Collection.class.isAssignableFrom(sourceType)) {
-                converter = lookup(sourceType);
-            }
-            if (converter == null) {
-                converter = lookup(String[].class);
-            }
-            return converter;
-        }
+    private void registerStandard(final boolean throwException, final boolean defaultNull) {
 
-        return lookup(targetType);
+        final Number     defaultNumber     = defaultNull ? null : ZERO;
+        final BigDecimal bigDecDeflt       = defaultNull ? null : new BigDecimal("0.0");
+        final BigInteger bigIntDeflt       = defaultNull ? null : new BigInteger("0");
+        final Boolean    booleanDefault    = defaultNull ? null : Boolean.FALSE;
+        final Character  charDefault       = defaultNull ? null : SPACE;
+        final String     stringDefault     = defaultNull ? null : "";
 
-    }
+        register(BigDecimal.class, throwException ? new BigDecimalConverter() : new BigDecimalConverter(bigDecDeflt));
+        register(BigInteger.class, throwException ? new BigIntegerConverter() : new BigIntegerConverter(bigIntDeflt));
+        register(Boolean.class,    throwException ? new BooleanConverter()    : new BooleanConverter(booleanDefault));
+        register(Byte.class,       throwException ? new ByteConverter()       : new ByteConverter(defaultNumber));
+        register(Character.class,  throwException ? new CharacterConverter()  : new CharacterConverter(charDefault));
+        register(Double.class,     throwException ? new DoubleConverter()     : new DoubleConverter(defaultNumber));
+        register(Float.class,      throwException ? new FloatConverter()      : new FloatConverter(defaultNumber));
+        register(Integer.class,    throwException ? new IntegerConverter()    : new IntegerConverter(defaultNumber));
+        register(Long.class,       throwException ? new LongConverter()       : new LongConverter(defaultNumber));
+        register(Short.class,      throwException ? new ShortConverter()      : new ShortConverter(defaultNumber));
+        register(String.class,     throwException ? new StringConverter()     : new StringConverter(stringDefault));
 
-    /**
-     * Register a custom {@link Converter} for the specified destination
-     * {@code Class}, replacing any previously registered Converter.
-     *
-     * @param converter Converter to be registered
-     * @param clazz Destination class for conversions performed by this
-     *  Converter
-     */
-    public void register(final Converter converter, final Class<?> clazz) {
-        converters.put(clazz, converter);
     }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/DefaultBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils2/DefaultBeanIntrospector.java
index 1aa378c9..154c2863 100644
--- a/src/main/java/org/apache/commons/beanutils2/DefaultBeanIntrospector.java
+++ b/src/main/java/org/apache/commons/beanutils2/DefaultBeanIntrospector.java
@@ -62,37 +62,6 @@ public class DefaultBeanIntrospector implements BeanIntrospector {
     private DefaultBeanIntrospector() {
     }
 
-    /**
-     * Performs introspection of a specific Java class. This implementation uses
-     * the {@code java.beans.Introspector.getBeanInfo()} method to obtain
-     * all property descriptors for the current class and adds them to the
-     * passed in introspection context.
-     *
-     * @param icontext the introspection context
-     */
-    @Override
-    public void introspect(final IntrospectionContext icontext) {
-        BeanInfo beanInfo = null;
-        try {
-            beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
-        } catch (final IntrospectionException e) {
-            // no descriptors are added to the context
-            log.error(
-                    "Error when inspecting class " + icontext.getTargetClass(),
-                    e);
-            return;
-        }
-
-        PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
-        if (descriptors == null) {
-            descriptors = PropertyDescriptors.EMPTY_ARRAY;
-        }
-
-        handleIndexedPropertyDescriptors(icontext.getTargetClass(),
-                descriptors);
-        icontext.addPropertyDescriptors(descriptors);
-    }
-
     /**
      * This method fixes an issue where IndexedPropertyDescriptor behaves
      * differently in different versions of the JDK for 'indexed' properties
@@ -176,4 +145,35 @@ public class DefaultBeanIntrospector implements BeanIntrospector {
             }
         }
     }
+
+    /**
+     * Performs introspection of a specific Java class. This implementation uses
+     * the {@code java.beans.Introspector.getBeanInfo()} method to obtain
+     * all property descriptors for the current class and adds them to the
+     * passed in introspection context.
+     *
+     * @param icontext the introspection context
+     */
+    @Override
+    public void introspect(final IntrospectionContext icontext) {
+        BeanInfo beanInfo = null;
+        try {
+            beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
+        } catch (final IntrospectionException e) {
+            // no descriptors are added to the context
+            log.error(
+                    "Error when inspecting class " + icontext.getTargetClass(),
+                    e);
+            return;
+        }
+
+        PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
+        if (descriptors == null) {
+            descriptors = PropertyDescriptors.EMPTY_ARRAY;
+        }
+
+        handleIndexedPropertyDescriptors(icontext.getTargetClass(),
+                descriptors);
+        icontext.addPropertyDescriptors(descriptors);
+    }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/DefaultIntrospectionContext.java b/src/main/java/org/apache/commons/beanutils2/DefaultIntrospectionContext.java
index 6ffd0396..af814cb5 100644
--- a/src/main/java/org/apache/commons/beanutils2/DefaultIntrospectionContext.java
+++ b/src/main/java/org/apache/commons/beanutils2/DefaultIntrospectionContext.java
@@ -53,11 +53,6 @@ class DefaultIntrospectionContext implements IntrospectionContext {
         descriptors = new HashMap<>();
     }
 
-    @Override
-    public Class<?> getTargetClass() {
-        return currentClass;
-    }
-
     @Override
     public void addPropertyDescriptor(final PropertyDescriptor desc) {
         if (desc == null) {
@@ -80,18 +75,28 @@ class DefaultIntrospectionContext implements IntrospectionContext {
     }
 
     @Override
-    public boolean hasProperty(final String name) {
-        return descriptors.containsKey(name);
+    public PropertyDescriptor getPropertyDescriptor(final String name) {
+        return descriptors.get(name);
+    }
+
+    /**
+     * Returns an array with all descriptors added to this context. This method
+     * is used to obtain the results of introspection.
+     *
+     * @return an array with all known property descriptors
+     */
+    public PropertyDescriptor[] getPropertyDescriptors() {
+        return descriptors.values().toArray(PropertyDescriptors.EMPTY_ARRAY);
     }
 
     @Override
-    public PropertyDescriptor getPropertyDescriptor(final String name) {
-        return descriptors.get(name);
+    public Class<?> getTargetClass() {
+        return currentClass;
     }
 
     @Override
-    public void removePropertyDescriptor(final String name) {
-        descriptors.remove(name);
+    public boolean hasProperty(final String name) {
+        return descriptors.containsKey(name);
     }
 
     @Override
@@ -99,13 +104,8 @@ class DefaultIntrospectionContext implements IntrospectionContext {
         return descriptors.keySet();
     }
 
-    /**
-     * Returns an array with all descriptors added to this context. This method
-     * is used to obtain the results of introspection.
-     *
-     * @return an array with all known property descriptors
-     */
-    public PropertyDescriptor[] getPropertyDescriptors() {
-        return descriptors.values().toArray(PropertyDescriptors.EMPTY_ARRAY);
+    @Override
+    public void removePropertyDescriptor(final String name) {
+        descriptors.remove(name);
     }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/DynaBean.java b/src/main/java/org/apache/commons/beanutils2/DynaBean.java
index 25bcf2ad..35a7f66a 100644
--- a/src/main/java/org/apache/commons/beanutils2/DynaBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/DynaBean.java
@@ -106,37 +106,37 @@ public interface DynaBean {
     void remove(String name, String key);
 
     /**
-     * Sets the value of a simple property with the specified name.
+     * Sets the value of an indexed property with the specified name.
      *
      * @param name Name of the property whose value is to be set
+     * @param index Index of the property to be set
      * @param value Value to which this property is to be set
      *
      * @throws ConversionException if the specified value cannot be
      *  converted to the type required for this property
      * @throws IllegalArgumentException if there is no property
      *  of the specified name
-     * @throws NullPointerException if an attempt is made to set a
-     *  primitive property to null
+     * @throws IllegalArgumentException if the specified property
+     *  exists, but is not indexed
+     * @throws IndexOutOfBoundsException if the specified index
+     *  is outside the range of the underlying property
      */
-    void set(String name, Object value);
+    void set(String name, int index, Object value);
 
     /**
-     * Sets the value of an indexed property with the specified name.
+     * Sets the value of a simple property with the specified name.
      *
      * @param name Name of the property whose value is to be set
-     * @param index Index of the property to be set
      * @param value Value to which this property is to be set
      *
      * @throws ConversionException if the specified value cannot be
      *  converted to the type required for this property
      * @throws IllegalArgumentException if there is no property
      *  of the specified name
-     * @throws IllegalArgumentException if the specified property
-     *  exists, but is not indexed
-     * @throws IndexOutOfBoundsException if the specified index
-     *  is outside the range of the underlying property
+     * @throws NullPointerException if an attempt is made to set a
+     *  primitive property to null
      */
-    void set(String name, int index, Object value);
+    void set(String name, Object value);
 
     /**
      * Sets the value of a mapped property with the specified name.
diff --git a/src/main/java/org/apache/commons/beanutils2/DynaBeanPropertyMapDecorator.java b/src/main/java/org/apache/commons/beanutils2/DynaBeanPropertyMapDecorator.java
index 9b8030e3..17e1e76d 100644
--- a/src/main/java/org/apache/commons/beanutils2/DynaBeanPropertyMapDecorator.java
+++ b/src/main/java/org/apache/commons/beanutils2/DynaBeanPropertyMapDecorator.java
@@ -61,26 +61,26 @@ package org.apache.commons.beanutils2;
 public class DynaBeanPropertyMapDecorator extends BaseDynaBeanMapDecorator<String> {
 
     /**
-     * Constructs a Map for the specified {@link DynaBean}.
+     * Constructs a read only Map for the specified
+     * {@link DynaBean}.
      *
      * @param dynaBean The dyna bean being decorated
-     * @param readOnly {@code true} if the Map is read only
-     * otherwise {@code false}
      * @throws IllegalArgumentException if the {@link DynaBean} is null.
      */
-    public DynaBeanPropertyMapDecorator(final DynaBean dynaBean, final boolean readOnly) {
-        super(dynaBean, readOnly);
+    public DynaBeanPropertyMapDecorator(final DynaBean dynaBean) {
+        super(dynaBean);
     }
 
     /**
-     * Constructs a read only Map for the specified
-     * {@link DynaBean}.
+     * Constructs a Map for the specified {@link DynaBean}.
      *
      * @param dynaBean The dyna bean being decorated
+     * @param readOnly {@code true} if the Map is read only
+     * otherwise {@code false}
      * @throws IllegalArgumentException if the {@link DynaBean} is null.
      */
-    public DynaBeanPropertyMapDecorator(final DynaBean dynaBean) {
-        super(dynaBean);
+    public DynaBeanPropertyMapDecorator(final DynaBean dynaBean, final boolean readOnly) {
+        super(dynaBean, readOnly);
     }
 
     @Override
diff --git a/src/main/java/org/apache/commons/beanutils2/DynaClass.java b/src/main/java/org/apache/commons/beanutils2/DynaClass.java
index 9aa7b6c8..3cb417fb 100644
--- a/src/main/java/org/apache/commons/beanutils2/DynaClass.java
+++ b/src/main/java/org/apache/commons/beanutils2/DynaClass.java
@@ -26,14 +26,17 @@ package org.apache.commons.beanutils2;
 public interface DynaClass {
 
     /**
-     * Returns the name of this DynaClass (analogous to the
-     * {@code getName()} method of {@code java.lang.Class}, which
-     * allows the same {@code DynaClass} implementation class to support
-     * different dynamic classes, with different sets of properties.
+     * <p>Returns an array of {@code PropertyDescriptor} for the properties
+     * currently defined in this DynaClass.  If no properties are defined, a
+     * zero-length array will be returned.</p>
      *
-     * @return the name of the DynaClass
+     * <p><strong>FIXME</strong> - Should we really be implementing
+     * {@code getBeanInfo()} instead, which returns property descriptors
+     * and a bunch of other stuff?</p>
+     *
+     * @return the set of properties for this DynaClass
      */
-    String getName();
+    DynaProperty[] getDynaProperties();
 
     /**
      * Returns a property descriptor for the specified property, if it exists;
@@ -48,17 +51,14 @@ public interface DynaClass {
     DynaProperty getDynaProperty(String name);
 
     /**
-     * <p>Returns an array of {@code PropertyDescriptor} for the properties
-     * currently defined in this DynaClass.  If no properties are defined, a
-     * zero-length array will be returned.</p>
-     *
-     * <p><strong>FIXME</strong> - Should we really be implementing
-     * {@code getBeanInfo()} instead, which returns property descriptors
-     * and a bunch of other stuff?</p>
+     * Returns the name of this DynaClass (analogous to the
+     * {@code getName()} method of {@code java.lang.Class}, which
+     * allows the same {@code DynaClass} implementation class to support
+     * different dynamic classes, with different sets of properties.
      *
-     * @return the set of properties for this DynaClass
+     * @return the name of the DynaClass
      */
-    DynaProperty[] getDynaProperties();
+    String getName();
 
     /**
      * Instantiates and return a new DynaBean instance, associated
diff --git a/src/main/java/org/apache/commons/beanutils2/DynaProperty.java b/src/main/java/org/apache/commons/beanutils2/DynaProperty.java
index ca2e34cd..a9054e95 100644
--- a/src/main/java/org/apache/commons/beanutils2/DynaProperty.java
+++ b/src/main/java/org/apache/commons/beanutils2/DynaProperty.java
@@ -57,6 +57,19 @@ public class DynaProperty implements Serializable {
     private static final int LONG_TYPE = 7;
     private static final int SHORT_TYPE = 8;
 
+    /**
+     * Empty array.
+     */
+    public static final DynaProperty[] EMPTY_ARRAY = {};
+
+    /** Property name */
+    protected String name;
+
+    /** Property type */
+    protected transient Class<?> type;
+
+    /** The <em>(optional)</em> type of content elements for indexed {@code DynaProperty} */
+    protected transient Class<?> contentType;
     /**
      * Constructs a property that accepts any data type.
      *
@@ -94,8 +107,46 @@ public class DynaProperty implements Serializable {
         this.contentType = contentType;
     }
 
-    /** Property name */
-    protected String name;
+    /**
+     * Checks this instance against the specified Object for equality. Overrides the
+     * default reference test for equality provided by {@link java.lang.Object#equals(Object)}
+     * @param obj The object to compare to
+     * @return {@code true} if object is a dyna property with the same name
+     * type and content type, otherwise {@code false}
+     * @since 1.8.0
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        boolean result;
+
+        result = obj == this;
+
+        if (!result && obj instanceof DynaProperty) {
+            final DynaProperty that = (DynaProperty) obj;
+            result =
+               (Objects.equals(this.name, that.name)) &&
+               (Objects.equals(this.type, that.type)) &&
+               (Objects.equals(this.contentType, that.contentType));
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets the <em>(optional)</em> type of the indexed content for {@code DynaProperty}'s
+     * that support this feature.
+     *
+     * <p>There are issues with serializing primitive class types on certain JVM versions
+     * (including java 1.3).
+     * Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p>
+     *
+     * @return the Class for the content type if this is an indexed {@code DynaProperty}
+     * and this feature is supported. Otherwise null.
+     */
+    public Class<?> getContentType() {
+        return contentType;
+    }
+
     /**
      * Get the name of this property.
      * @return the name of the property
@@ -104,9 +155,6 @@ public class DynaProperty implements Serializable {
         return this.name;
     }
 
-    /** Property type */
-    protected transient Class<?> type;
-
     /**
      * <p>Gets the Java class representing the data type of the underlying property
      * values.</p>
@@ -123,27 +171,20 @@ public class DynaProperty implements Serializable {
         return this.type;
     }
 
-    /** The <em>(optional)</em> type of content elements for indexed {@code DynaProperty} */
-    protected transient Class<?> contentType;
-
     /**
-     * Empty array.
+     * @return the hashcode for this dyna property
+     * @see Object#hashCode
+     * @since 1.8.0
      */
-    public static final DynaProperty[] EMPTY_ARRAY = {};
+    @Override
+    public int hashCode() {
+       int result = 1;
 
-    /**
-     * Gets the <em>(optional)</em> type of the indexed content for {@code DynaProperty}'s
-     * that support this feature.
-     *
-     * <p>There are issues with serializing primitive class types on certain JVM versions
-     * (including java 1.3).
-     * Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p>
-     *
-     * @return the Class for the content type if this is an indexed {@code DynaProperty}
-     * and this feature is supported. Otherwise null.
-     */
-    public Class<?> getContentType() {
-        return contentType;
+       result = result * 31 + (name == null ? 0 : name.hashCode());
+       result = result * 31 + (type == null ? 0 : type.hashCode());
+       result = result * 31 + (contentType == null ? 0 : contentType.hashCode());
+
+       return result;
     }
 
     /**
@@ -177,44 +218,55 @@ public class DynaProperty implements Serializable {
     }
 
     /**
-     * Checks this instance against the specified Object for equality. Overrides the
-     * default reference test for equality provided by {@link java.lang.Object#equals(Object)}
-     * @param obj The object to compare to
-     * @return {@code true} if object is a dyna property with the same name
-     * type and content type, otherwise {@code false}
-     * @since 1.8.0
+     * Reads a class using safe encoding to workaround java 1.3 serialization bug.
      */
-    @Override
-    public boolean equals(final Object obj) {
-        boolean result;
+    private Class<?> readAnyClass(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+        // read back type class safely
+        if (in.readBoolean()) {
+            // it's a type constant
+            switch (in.readInt()) {
 
-        result = obj == this;
+                case BOOLEAN_TYPE: return   Boolean.TYPE;
+                case BYTE_TYPE:    return      Byte.TYPE;
+                case CHAR_TYPE:    return Character.TYPE;
+                case DOUBLE_TYPE:  return    Double.TYPE;
+                case FLOAT_TYPE:   return     Float.TYPE;
+                case INT_TYPE:     return   Integer.TYPE;
+                case LONG_TYPE:    return      Long.TYPE;
+                case SHORT_TYPE:   return     Short.TYPE;
+                default:
+                    // something's gone wrong
+                    throw new StreamCorruptedException(
+                        "Invalid primitive type. "
+                        + "Check version of beanutils used to serialize is compatible.");
 
-        if (!result && obj instanceof DynaProperty) {
-            final DynaProperty that = (DynaProperty) obj;
-            result =
-               (Objects.equals(this.name, that.name)) &&
-               (Objects.equals(this.type, that.type)) &&
-               (Objects.equals(this.contentType, that.contentType));
-        }
+            }
 
-        return result;
+        }
+        // it's another class
+        return (Class<?>) in.readObject();
     }
 
     /**
-     * @return the hashcode for this dyna property
-     * @see Object#hashCode
-     * @since 1.8.0
+     * Reads field values for this object safely.
+     * There are issues with serializing primitive class types on certain JVM versions
+     * (including java 1.3).
+     * This method provides a workaround.
+     *
+     * @param in {@link ObjectInputStream} to read object from
+     * @throws StreamCorruptedException when the stream data values are outside expected range
+     * @throws IOException if the input stream can't be read
+     * @throws ClassNotFoundException When trying to read an object of class that is not on the classpath
      */
-    @Override
-    public int hashCode() {
-       int result = 1;
+    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+        this.type = readAnyClass(in);
 
-       result = result * 31 + (name == null ? 0 : name.hashCode());
-       result = result * 31 + (type == null ? 0 : type.hashCode());
-       result = result * 31 + (contentType == null ? 0 : contentType.hashCode());
+        if (isMapped() || isIndexed()) {
+            this.contentType = readAnyClass(in);
+        }
 
-       return result;
+        // read other values
+        in.defaultReadObject();
     }
 
     /**
@@ -234,26 +286,6 @@ public class DynaProperty implements Serializable {
         return sb.toString();
     }
 
-    /**
-     * Writes this object safely.
-     * There are issues with serializing primitive class types on certain JVM versions
-     * (including java 1.3).
-     * This method provides a workaround.
-     *
-     * @param out {@link ObjectOutputStream} to write object to
-     * @throws IOException if the object can't be written
-     */
-    private void writeObject(final ObjectOutputStream out) throws IOException {
-        writeAnyClass(this.type,out);
-
-        if (isMapped() || isIndexed()) {
-            writeAnyClass(this.contentType,out);
-        }
-
-        // write out other values
-        out.defaultWriteObject();
-    }
-
     /**
      * Write a class using safe encoding to workaround java 1.3 serialization bug.
      */
@@ -290,54 +322,22 @@ public class DynaProperty implements Serializable {
     }
 
     /**
-     * Reads field values for this object safely.
+     * Writes this object safely.
      * There are issues with serializing primitive class types on certain JVM versions
      * (including java 1.3).
      * This method provides a workaround.
      *
-     * @param in {@link ObjectInputStream} to read object from
-     * @throws StreamCorruptedException when the stream data values are outside expected range
-     * @throws IOException if the input stream can't be read
-     * @throws ClassNotFoundException When trying to read an object of class that is not on the classpath
+     * @param out {@link ObjectOutputStream} to write object to
+     * @throws IOException if the object can't be written
      */
-    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
-        this.type = readAnyClass(in);
+    private void writeObject(final ObjectOutputStream out) throws IOException {
+        writeAnyClass(this.type,out);
 
         if (isMapped() || isIndexed()) {
-            this.contentType = readAnyClass(in);
+            writeAnyClass(this.contentType,out);
         }
 
-        // read other values
-        in.defaultReadObject();
-    }
-
-    /**
-     * Reads a class using safe encoding to workaround java 1.3 serialization bug.
-     */
-    private Class<?> readAnyClass(final ObjectInputStream in) throws IOException, ClassNotFoundException {
-        // read back type class safely
-        if (in.readBoolean()) {
-            // it's a type constant
-            switch (in.readInt()) {
-
-                case BOOLEAN_TYPE: return   Boolean.TYPE;
-                case BYTE_TYPE:    return      Byte.TYPE;
-                case CHAR_TYPE:    return Character.TYPE;
-                case DOUBLE_TYPE:  return    Double.TYPE;
-                case FLOAT_TYPE:   return     Float.TYPE;
-                case INT_TYPE:     return   Integer.TYPE;
-                case LONG_TYPE:    return      Long.TYPE;
-                case SHORT_TYPE:   return     Short.TYPE;
-                default:
-                    // something's gone wrong
-                    throw new StreamCorruptedException(
-                        "Invalid primitive type. "
-                        + "Check version of beanutils used to serialize is compatible.");
-
-            }
-
-        }
-        // it's another class
-        return (Class<?>) in.readObject();
+        // write out other values
+        out.defaultWriteObject();
     }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java
index 14389451..93c15aec 100644
--- a/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java
+++ b/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java
@@ -85,6 +85,15 @@ public class FluentPropertyBeanIntrospector implements BeanIntrospector {
     /** The prefix of write methods to search for. */
     private final String writeMethodPrefix;
 
+    /**
+     *
+     * Creates a new instance of {@code FluentPropertyBeanIntrospector} and
+     * sets the default prefix for write methods.
+     */
+    public FluentPropertyBeanIntrospector() {
+        this(DEFAULT_WRITE_METHOD_PREFIX);
+    }
+
     /**
      *
      * Creates a new instance of {@code FluentPropertyBeanIntrospector} and
@@ -103,12 +112,16 @@ public class FluentPropertyBeanIntrospector implements BeanIntrospector {
     }
 
     /**
+     * Creates a property descriptor for a fluent API property.
      *
-     * Creates a new instance of {@code FluentPropertyBeanIntrospector} and
-     * sets the default prefix for write methods.
+     * @param m the set method for the fluent API property
+     * @param propertyName the name of the corresponding property
+     * @return the descriptor
+     * @throws IntrospectionException if an error occurs
      */
-    public FluentPropertyBeanIntrospector() {
-        this(DEFAULT_WRITE_METHOD_PREFIX);
+    private PropertyDescriptor createFluentPropertyDescritor(final Method m,
+            final String propertyName) throws IntrospectionException {
+        return new PropertyDescriptor(propertyName(m), null, m);
     }
 
     /**
@@ -165,17 +178,4 @@ public class FluentPropertyBeanIntrospector implements BeanIntrospector {
         return methodName.length() > 1 ? Introspector.decapitalize(methodName) : methodName
                 .toLowerCase(Locale.ENGLISH);
     }
-
-    /**
-     * Creates a property descriptor for a fluent API property.
-     *
-     * @param m the set method for the fluent API property
-     * @param propertyName the name of the corresponding property
-     * @return the descriptor
-     * @throws IntrospectionException if an error occurs
-     */
-    private PropertyDescriptor createFluentPropertyDescritor(final Method m,
-            final String propertyName) throws IntrospectionException {
-        return new PropertyDescriptor(propertyName(m), null, m);
-    }
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/beanutils2/IntrospectionContext.java b/src/main/java/org/apache/commons/beanutils2/IntrospectionContext.java
index bfe2fda0..f83c49a4 100644
--- a/src/main/java/org/apache/commons/beanutils2/IntrospectionContext.java
+++ b/src/main/java/org/apache/commons/beanutils2/IntrospectionContext.java
@@ -37,13 +37,6 @@ import java.util.Set;
  */
 public interface IntrospectionContext {
 
-    /**
-     * Returns the class that is subject of introspection.
-     *
-     * @return the current class
-     */
-    Class<?> getTargetClass();
-
     /**
      * Adds the given property descriptor to this context. This method is called
      * by a {@code BeanIntrospector} during introspection for each detected
@@ -62,6 +55,23 @@ public interface IntrospectionContext {
      */
     void addPropertyDescriptors(PropertyDescriptor[] descriptors);
 
+    /**
+     * Returns the descriptor for the property with the given name or
+     * <b>null</b> if this property is unknown.
+     *
+     * @param name the name of the property in question
+     * @return the descriptor for this property or <b>null</b> if this property
+     *         is unknown
+     */
+    PropertyDescriptor getPropertyDescriptor(String name);
+
+    /**
+     * Returns the class that is subject of introspection.
+     *
+     * @return the current class
+     */
+    Class<?> getTargetClass();
+
     /**
      * Tests whether a descriptor for the property with the given name is
      * already contained in this context. This method can be used for instance
@@ -74,14 +84,11 @@ public interface IntrospectionContext {
     boolean hasProperty(String name);
 
     /**
-     * Returns the descriptor for the property with the given name or
-     * <b>null</b> if this property is unknown.
+     * Returns a set with the names of all properties known to this context.
      *
-     * @param name the name of the property in question
-     * @return the descriptor for this property or <b>null</b> if this property
-     *         is unknown
+     * @return a set with the known property names
      */
-    PropertyDescriptor getPropertyDescriptor(String name);
+    Set<String> propertyNames();
 
     /**
      * Removes the descriptor for the property with the given name.
@@ -89,11 +96,4 @@ public interface IntrospectionContext {
      * @param name the name of the affected property
      */
     void removePropertyDescriptor(String name);
-
-    /**
-     * Returns a set with the names of all properties known to this context.
-     *
-     * @return a set with the known property names
-     */
-    Set<String> propertyNames();
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/LazyDynaBean.java b/src/main/java/org/apache/commons/beanutils2/LazyDynaBean.java
index 9433d4c7..4425bd6b 100644
--- a/src/main/java/org/apache/commons/beanutils2/LazyDynaBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/LazyDynaBean.java
@@ -148,6 +148,8 @@ public class LazyDynaBean implements DynaBean, Serializable {
     /** Double Zero */
     protected static final Double     Double_ZERO     = Double.valueOf((byte)0);
 
+    static final LazyDynaBean[] EMPTY_ARRAY = {};
+
     /**
      * The {@code MutableDynaClass} "base class" that this DynaBean
      * is associated with.
@@ -163,8 +165,6 @@ public class LazyDynaBean implements DynaBean, Serializable {
      */
     protected MutableDynaClass dynaClass;
 
-    static final LazyDynaBean[] EMPTY_ARRAY = {};
-
     /**
      * Constructs a new {@code LazyDynaBean</code> with a <code>LazyDynaClass} instance.
      */
@@ -172,15 +172,6 @@ public class LazyDynaBean implements DynaBean, Serializable {
         this(new LazyDynaClass());
     }
 
-    /**
-     * Constructs a new {@code LazyDynaBean</code> with a <code>LazyDynaClass} instance.
-     *
-     * @param name Name of this DynaBean class
-     */
-    public LazyDynaBean(final String name) {
-        this(new LazyDynaClass(name));
-    }
-
     /**
      * Constructs a new {@code DynaBean} associated with the specified
      * {@code DynaClass</code> instance - if its not a <code>MutableDynaClass}
@@ -199,57 +190,14 @@ public class LazyDynaBean implements DynaBean, Serializable {
     }
 
     /**
-     * <p>
-     * Gets a Map representation of this DynaBean.
-     * </p>
-     * This, for example, could be used in JSTL in the following way to access
-     * a DynaBean's {@code fooProperty}:
-     * <ul><li>{@code ${myDynaBean.<b>map</b>.fooProperty}}</li></ul>
-     *
-     * @return a Map representation of this DynaBean
-     */
-    public Map<String, Object> getMap() {
-        // cache the Map
-        if (mapDecorator == null) {
-            mapDecorator = new DynaBeanPropertyMapDecorator(this);
-        }
-        return mapDecorator;
-    }
-
-    /**
-     * <p>Return the size of an indexed or mapped property.</p>
+     * Constructs a new {@code LazyDynaBean</code> with a <code>LazyDynaClass} instance.
      *
-     * @param name Name of the property
-     * @return The indexed or mapped property size
-     * @throws IllegalArgumentException if no property name is specified
+     * @param name Name of this DynaBean class
      */
-    public int size(final String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("No property name specified");
-        }
-
-        final Object value = values.get(name);
-        if (value == null) {
-            return 0;
-        }
-
-        if (value instanceof Map) {
-            return ((Map<?, ?>)value).size();
-        }
-
-        if (value instanceof List) {
-            return ((List<?>)value).size();
-        }
-
-        if (value.getClass().isArray()) {
-            return Array.getLength(value);
-        }
-
-        return 0;
+    public LazyDynaBean(final String name) {
+        this(new LazyDynaClass(name));
     }
 
-
-
     /**
      * Does the specified mapped property contain a value for the specified
      * key value?
@@ -280,233 +228,287 @@ public class LazyDynaBean implements DynaBean, Serializable {
     }
 
     /**
-     * <p>Return the value of a simple property with the specified name.</p>
-     *
-     * <p><strong>N.B.</strong> Returns {@code null} if there is no property
-     *  of the specified name.</p>
-     *
-     * @param name Name of the property whose value is to be retrieved.
-     * @return The property's value
-     * @throws IllegalArgumentException if no property name is specified
+     * Create a new Instance of a 'DynaBean' Property.
+     * @param name The name of the property
+     * @param type The class of the property
+     * @return The new value
      */
-    @Override
-    public Object get(final String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("No property name specified");
-        }
-
-        // Value found
-        Object value = values.get(name);
-        if (value != null) {
-            return value;
+    protected Object createDynaBeanProperty(final String name, final Class<?> type) {
+        try {
+            return type.newInstance();
         }
-
-        // Property doesn't exist
-        if (!isDynaProperty(name)) {
+        catch (final Exception ex) {
+            if (logger().isWarnEnabled()) {
+                logger().warn("Error instantiating DynaBean property of type '" +
+                        type.getName() + "' for '" + name + "' ", ex);
+            }
             return null;
         }
+    }
 
-        // Property doesn't exist
-        value = createProperty(name, dynaClass.getDynaProperty(name).getType());
-
-        if (value != null) {
-            set(name, value);
-        }
 
-        return value;
-    }
 
     /**
-     * <p>Return the value of an indexed property with the specified name.</p>
-     *
-     * <p><strong>N.B.</strong> Returns {@code null} if there is no 'indexed'
-     * property of the specified name.</p>
-     *
-     * @param name Name of the property whose value is to be retrieved
-     * @param index Index of the value to be retrieved
-     * @return The indexed property's value
-     *
-     * @throws IllegalArgumentException if the specified property
-     *  exists, but is not indexed
-     * @throws IndexOutOfBoundsException if the specified index
-     *  is outside the range of the underlying property
+     * Create a new Instance of an 'Indexed' Property
+     * @param name The name of the property
+     * @param type The class of the property
+     * @return The new value
      */
-    @Override
-    public Object get(final String name, final int index) {
-        // If its not a property, then create default indexed property
-        if (!isDynaProperty(name)) {
-            set(name, defaultIndexedProperty(name));
-        }
+    protected Object createIndexedProperty(final String name, final Class<?> type) {
+        // Create the indexed object
+        Object indexedProperty = null;
 
-        // Get the indexed property
-        Object indexedProperty = get(name);
+        if (type == null) {
 
-        // Check that the property is indexed
-        if (!dynaClass.getDynaProperty(name).isIndexed()) {
-            throw new IllegalArgumentException
-                ("Non-indexed property for '" + name + "[" + index + "]' "
-                                      + dynaClass.getDynaProperty(name).getName());
-        }
+            indexedProperty = defaultIndexedProperty(name);
 
-        // Grow indexed property to appropriate size
-        indexedProperty = growIndexedProperty(name, indexedProperty, index);
+        } else if (type.isArray()) {
 
-        // Return the indexed value
-        if (indexedProperty.getClass().isArray()) {
-            return Array.get(indexedProperty, index);
-        }
-        if (indexedProperty instanceof List) {
-            return ((List<?>)indexedProperty).get(index);
+            indexedProperty = Array.newInstance(type.getComponentType(), 0);
+
+        } else if (List.class.isAssignableFrom(type)) {
+            if (type.isInterface()) {
+                indexedProperty = defaultIndexedProperty(name);
+            } else {
+                try {
+                    indexedProperty = type.newInstance();
+                }
+                catch (final Exception ex) {
+                    throw new IllegalArgumentException
+                        ("Error instantiating indexed property of type '" +
+                                   type.getName() + "' for '" + name + "' " + ex);
+                }
+            }
+        } else {
+
+            throw new IllegalArgumentException
+                    ("Non-indexed property of type '" + type.getName() + "' for '" + name + "'");
         }
-        throw new IllegalArgumentException
-            ("Non-indexed property for '" + name + "[" + index + "]' "
-                              + indexedProperty.getClass().getName());
+
+        return indexedProperty;
     }
 
     /**
-     * <p>Return the value of a mapped property with the specified name.</p>
-     *
-     * <p><strong>N.B.</strong> Returns {@code null} if there is no 'mapped'
-     * property of the specified name.</p>
-     *
-     * @param name Name of the property whose value is to be retrieved
-     * @param key Key of the value to be retrieved
-     * @return The mapped property's value
-     *
-     * @throws IllegalArgumentException if the specified property
-     *  exists, but is not mapped
+     * Create a new Instance of a 'Mapped' Property
+     * @param name The name of the property
+     * @param type The class of the property
+     * @return The new value
      */
-    @Override
-    public Object get(final String name, final String key) {
-        // If its not a property, then create default mapped property
-        if (!isDynaProperty(name)) {
-            set(name, defaultMappedProperty(name));
-        }
+    protected Object createMappedProperty(final String name, final Class<?> type) {
+        // Create the mapped object
+        Object mappedProperty = null;
 
-        // Get the mapped property
-        final Object mappedProperty = get(name);
+        if ((type == null) || type.isInterface()) {
+
+            mappedProperty = defaultMappedProperty(name);
+
+        } else if (Map.class.isAssignableFrom(type)) {
+            try {
+                mappedProperty = type.newInstance();
+            }
+            catch (final Exception ex) {
+                throw new IllegalArgumentException
+                    ("Error instantiating mapped property of type '" +
+                            type.getName() + "' for '" + name + "' " + ex);
+            }
+        } else {
 
-        // Check that the property is mapped
-        if (!dynaClass.getDynaProperty(name).isMapped()) {
             throw new IllegalArgumentException
-                ("Non-mapped property for '" + name + "(" + key + ")' "
-                            + dynaClass.getDynaProperty(name).getType().getName());
+                    ("Non-mapped property of type '" + type.getName() + "' for '" + name + "'");
         }
 
-        // Get the value from the Map
-        if (mappedProperty instanceof Map) {
-            return ((Map<?, ?>) mappedProperty).get(key);
-        }
-        throw new IllegalArgumentException
-          ("Non-mapped property for '" + name + "(" + key + ")'"
-                              + mappedProperty.getClass().getName());
+        return mappedProperty;
     }
 
     /**
-     * Gets the {@code DynaClass} instance that describes the set of
-     * properties available for this DynaBean.
-     *
-     * @return The associated DynaClass
+     * Create a new Instance of a {@code java.lang.Number} Property.
+     * @param name The name of the property
+     * @param type The class of the property
+     * @return The new value
      */
-    @Override
-    public DynaClass getDynaClass() {
-        return dynaClass;
+    protected Object createNumberProperty(final String name, final Class<?> type) {
+        return null;
     }
 
     /**
-     * Remove any existing value for the specified key on the
-     * specified mapped property.
-     *
-     * @param name Name of the property for which a value is to
-     *  be removed
-     * @param key Key of the value to be removed
-     *
-     * @throws IllegalArgumentException if there is no property
-     *  of the specified name
+     * Create a new Instance of other Property types
+     * @param name The name of the property
+     * @param type The class of the property
+     * @return The new value
      */
-    @Override
-    public void remove(final String name, final String key) {
-        if (name == null) {
-            throw new IllegalArgumentException("No property name specified");
+    protected Object createOtherProperty(final String name, final Class<?> type) {
+        if (type == Object.class    ||
+            type == String.class    ||
+            type == Boolean.class   ||
+            type == Character.class ||
+            Date.class.isAssignableFrom(type)) {
+
+            return null;
+
         }
 
-        final Object value = values.get(name);
-        if (value == null) {
-            return;
+        try {
+            return type.newInstance();
         }
+        catch (final Exception ex) {
+            if (logger().isWarnEnabled()) {
+                logger().warn("Error instantiating property of type '" + type.getName() + "' for '" + name + "' ", ex);
+            }
+            return null;
+        }
+    }
 
-        if (!(value instanceof Map)) {
-            throw new IllegalArgumentException
-                    ("Non-mapped property for '" + name + "(" + key + ")'"
-                            + value.getClass().getName());
+    /**
+     * Create a new Instance of a 'Primitive' Property.
+     * @param name The name of the property
+     * @param type The class of the property
+     * @return The new value
+     */
+    protected Object createPrimitiveProperty(final String name, final Class<?> type) {
+        if (type == Boolean.TYPE) {
+            return Boolean.FALSE;
         }
-        ((Map<?, ?>) value).remove(key);
+        if (type == Integer.TYPE) {
+            return Integer_ZERO;
+        }
+        if (type == Long.TYPE) {
+            return Long_ZERO;
+        }
+        if (type == Double.TYPE) {
+            return Double_ZERO;
+        }
+        if (type == Float.TYPE) {
+            return Float_ZERO;
+        }
+        if (type == Byte.TYPE) {
+            return Byte_ZERO;
+        }
+        if (type == Short.TYPE) {
+            return Short_ZERO;
+        }
+        if (type == Character.TYPE) {
+            return Character_SPACE;
+        }
+        return null;
     }
 
     /**
-     * Sets the value of a simple property with the specified name.
+     * Create a new Instance of a Property
+     * @param name The name of the property
+     * @param type The class of the property
+     * @return The new value
+     */
+    protected Object createProperty(final String name, final Class<?> type) {
+        if (type == null) {
+            return null;
+        }
+
+        // Create Lists, arrays or DynaBeans
+        if (type.isArray() || List.class.isAssignableFrom(type)) {
+            return createIndexedProperty(name, type);
+        }
+
+        if (Map.class.isAssignableFrom(type)) {
+            return createMappedProperty(name, type);
+        }
+
+        if (DynaBean.class.isAssignableFrom(type)) {
+            return createDynaBeanProperty(name, type);
+        }
+
+        if (type.isPrimitive()) {
+            return createPrimitiveProperty(name, type);
+        }
+
+        if (Number.class.isAssignableFrom(type)) {
+            return createNumberProperty(name, type);
+        }
+
+        return createOtherProperty(name, type);
+    }
+
+    /**
+     * <p>Creates a new {@code ArrayList} for an 'indexed' property
+     *    which doesn't exist.</p>
      *
-     * @param name Name of the property whose value is to be set
-     * @param value Value to which this property is to be set
+     * <p>This method should be overridden if an alternative {@code List}
+     *    or {@code Array} implementation is required for 'indexed' properties.</p>
      *
-     * @throws IllegalArgumentException if this is not an existing property
-     *  name for our DynaClass and the MutableDynaClass is restricted
-     * @throws ConversionException if the specified value cannot be
-     *  converted to the type required for this property
-     * @throws NullPointerException if an attempt is made to set a
-     *  primitive property to null
+     * @param name Name of the 'indexed property.
+     * @return The default value for an indexed property (java.util.ArrayList)
+     */
+    protected Object defaultIndexedProperty(final String name) {
+        return new ArrayList<>();
+    }
+
+    /**
+     * <p>Creates a new {@code HashMap} for a 'mapped' property
+     *    which doesn't exist.</p>
+     *
+     * <p>This method can be overridden if an alternative {@code Map}
+     *    implementation is required for 'mapped' properties.</p>
+     *
+     * @param name Name of the 'mapped property.
+     * @return The default value for a mapped property (java.util.HashMap)
+     */
+    protected Map<String, Object> defaultMappedProperty(final String name) {
+        return new HashMap<>();
+    }
+
+    /**
+     * <p>Return the value of a simple property with the specified name.</p>
+     *
+     * <p><strong>N.B.</strong> Returns {@code null} if there is no property
+     *  of the specified name.</p>
+     *
+     * @param name Name of the property whose value is to be retrieved.
+     * @return The property's value
+     * @throws IllegalArgumentException if no property name is specified
      */
     @Override
-    public void set(final String name, final Object value) {
-        // If the property doesn't exist, then add it
-        if (!isDynaProperty(name)) {
+    public Object get(final String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("No property name specified");
+        }
 
-            if (dynaClass.isRestricted()) {
-                throw new IllegalArgumentException
-                    ("Invalid property name '" + name + "' (DynaClass is restricted)");
-            }
-            if (value == null) {
-                dynaClass.add(name);
-            } else {
-                dynaClass.add(name, value.getClass());
-            }
+        // Value found
+        Object value = values.get(name);
+        if (value != null) {
+            return value;
+        }
 
+        // Property doesn't exist
+        if (!isDynaProperty(name)) {
+            return null;
         }
 
-        final DynaProperty descriptor = dynaClass.getDynaProperty(name);
+        // Property doesn't exist
+        value = createProperty(name, dynaClass.getDynaProperty(name).getType());
 
-        if (value == null) {
-            if (descriptor.getType().isPrimitive()) {
-                throw new NullPointerException
-                        ("Primitive value for '" + name + "'");
-            }
-        } else if (!isAssignable(descriptor.getType(), value.getClass())) {
-            throw ConversionException.format
-                    ("Cannot assign value of type '%s' to property '%s' of type '%s'", value.getClass().getName(), name, descriptor.getType().getName());
+        if (value != null) {
+            set(name, value);
         }
 
-        // Set the property's value
-        values.put(name, value);
+        return value;
     }
 
     /**
-     * Sets the value of an indexed property with the specified name.
+     * <p>Return the value of an indexed property with the specified name.</p>
      *
-     * @param name Name of the property whose value is to be set
-     * @param index Index of the property to be set
-     * @param value Value to which this property is to be set
+     * <p><strong>N.B.</strong> Returns {@code null} if there is no 'indexed'
+     * property of the specified name.</p>
+     *
+     * @param name Name of the property whose value is to be retrieved
+     * @param index Index of the value to be retrieved
+     * @return The indexed property's value
      *
-     * @throws ConversionException if the specified value cannot be
-     *  converted to the type required for this property
-     * @throws IllegalArgumentException if there is no property
-     *  of the specified name
      * @throws IllegalArgumentException if the specified property
      *  exists, but is not indexed
      * @throws IndexOutOfBoundsException if the specified index
      *  is outside the range of the underlying property
      */
     @Override
-    public void set(final String name, final int index, final Object value) {
+    public Object get(final String name, final int index) {
         // If its not a property, then create default indexed property
         if (!isDynaProperty(name)) {
             set(name, defaultIndexedProperty(name));
@@ -518,46 +520,41 @@ public class LazyDynaBean implements DynaBean, Serializable {
         // Check that the property is indexed
         if (!dynaClass.getDynaProperty(name).isIndexed()) {
             throw new IllegalArgumentException
-                ("Non-indexed property for '" + name + "[" + index + "]'"
-                            + dynaClass.getDynaProperty(name).getType().getName());
+                ("Non-indexed property for '" + name + "[" + index + "]' "
+                                      + dynaClass.getDynaProperty(name).getName());
         }
 
         // Grow indexed property to appropriate size
         indexedProperty = growIndexedProperty(name, indexedProperty, index);
 
-        // Set the value in an array
+        // Return the indexed value
         if (indexedProperty.getClass().isArray()) {
-            Array.set(indexedProperty, index, value);
-        } else if (indexedProperty instanceof List) {
-            @SuppressWarnings("unchecked")
-            final
-            // Indexed properties are stored in a List<Object>
-            List<Object> values = (List<Object>) indexedProperty;
-            values.set(index, value);
-        } else {
-            throw new IllegalArgumentException
-                ("Non-indexed property for '" + name + "[" + index + "]' "
-                            + indexedProperty.getClass().getName());
+            return Array.get(indexedProperty, index);
         }
+        if (indexedProperty instanceof List) {
+            return ((List<?>)indexedProperty).get(index);
+        }
+        throw new IllegalArgumentException
+            ("Non-indexed property for '" + name + "[" + index + "]' "
+                              + indexedProperty.getClass().getName());
     }
 
     /**
-     * Sets the value of a mapped property with the specified name.
+     * <p>Return the value of a mapped property with the specified name.</p>
      *
-     * @param name Name of the property whose value is to be set
-     * @param key Key of the property to be set
-     * @param value Value to which this property is to be set
+     * <p><strong>N.B.</strong> Returns {@code null} if there is no 'mapped'
+     * property of the specified name.</p>
+     *
+     * @param name Name of the property whose value is to be retrieved
+     * @param key Key of the value to be retrieved
+     * @return The mapped property's value
      *
-     * @throws ConversionException if the specified value cannot be
-     *  converted to the type required for this property
-     * @throws IllegalArgumentException if there is no property
-     *  of the specified name
      * @throws IllegalArgumentException if the specified property
      *  exists, but is not mapped
      */
     @Override
-    public void set(final String name, final String key, final Object value) {
-        // If the 'mapped' property doesn't exist, then add it
+    public Object get(final String name, final String key) {
+        // If its not a property, then create default mapped property
         if (!isDynaProperty(name)) {
             set(name, defaultMappedProperty(name));
         }
@@ -568,23 +565,53 @@ public class LazyDynaBean implements DynaBean, Serializable {
         // Check that the property is mapped
         if (!dynaClass.getDynaProperty(name).isMapped()) {
             throw new IllegalArgumentException
-                ("Non-mapped property for '" + name + "(" + key + ")'"
+                ("Non-mapped property for '" + name + "(" + key + ")' "
                             + dynaClass.getDynaProperty(name).getType().getName());
         }
 
-        // Set the value in the Map
-        @SuppressWarnings("unchecked")
-        final
-        // mapped properties are stored in a Map<String, Object>
-        Map<String, Object> valuesMap = (Map<String, Object>) mappedProperty;
-        valuesMap.put(key, value);
+        // Get the value from the Map
+        if (mappedProperty instanceof Map) {
+            return ((Map<?, ?>) mappedProperty).get(key);
+        }
+        throw new IllegalArgumentException
+          ("Non-mapped property for '" + name + "(" + key + ")'"
+                              + mappedProperty.getClass().getName());
     }
 
     /**
-     * Grow the size of an indexed property
-     * @param name The name of the property
-     * @param indexedProperty The current property value
-     * @param index The indexed value to grow the property to (i.e. one less than
+     * Gets the {@code DynaClass} instance that describes the set of
+     * properties available for this DynaBean.
+     *
+     * @return The associated DynaClass
+     */
+    @Override
+    public DynaClass getDynaClass() {
+        return dynaClass;
+    }
+
+    /**
+     * <p>
+     * Gets a Map representation of this DynaBean.
+     * </p>
+     * This, for example, could be used in JSTL in the following way to access
+     * a DynaBean's {@code fooProperty}:
+     * <ul><li>{@code ${myDynaBean.<b>map</b>.fooProperty}}</li></ul>
+     *
+     * @return a Map representation of this DynaBean
+     */
+    public Map<String, Object> getMap() {
+        // cache the Map
+        if (mapDecorator == null) {
+            mapDecorator = new DynaBeanPropertyMapDecorator(this);
+        }
+        return mapDecorator;
+    }
+
+    /**
+     * Grow the size of an indexed property
+     * @param name The name of the property
+     * @param indexedProperty The current property value
+     * @param index The indexed value to grow the property to (i.e. one less than
      * the required size)
      * @return The new property value (grown to the appropriate size)
      */
@@ -628,291 +655,264 @@ public class LazyDynaBean implements DynaBean, Serializable {
     }
 
     /**
-     * Create a new Instance of a Property
-     * @param name The name of the property
-     * @param type The class of the property
-     * @return The new value
+     * Is an object of the source class assignable to the destination class?
+     *
+     * @param dest Destination class
+     * @param source Source class
+     * @return {@code true} if the source class is assignable to the
+     * destination class, otherwise {@code false}
      */
-    protected Object createProperty(final String name, final Class<?> type) {
-        if (type == null) {
-            return null;
-        }
-
-        // Create Lists, arrays or DynaBeans
-        if (type.isArray() || List.class.isAssignableFrom(type)) {
-            return createIndexedProperty(name, type);
-        }
-
-        if (Map.class.isAssignableFrom(type)) {
-            return createMappedProperty(name, type);
-        }
-
-        if (DynaBean.class.isAssignableFrom(type)) {
-            return createDynaBeanProperty(name, type);
-        }
-
-        if (type.isPrimitive()) {
-            return createPrimitiveProperty(name, type);
-        }
-
-        if (Number.class.isAssignableFrom(type)) {
-            return createNumberProperty(name, type);
+    protected boolean isAssignable(final Class<?> dest, final Class<?> source) {
+        if (dest.isAssignableFrom(source) ||
+                dest == Boolean.TYPE && source == Boolean.class ||
+                dest == Byte.TYPE && source == Byte.class ||
+                dest == Character.TYPE && source == Character.class ||
+                dest == Double.TYPE && source == Double.class ||
+                dest == Float.TYPE && source == Float.class ||
+                dest == Integer.TYPE && source == Integer.class ||
+                dest == Long.TYPE && source == Long.class ||
+                dest == Short.TYPE && source == Short.class) {
+            return true;
         }
+        return false;
 
-        return createOtherProperty(name, type);
     }
 
     /**
-     * Create a new Instance of an 'Indexed' Property
-     * @param name The name of the property
-     * @param type The class of the property
-     * @return The new value
+     * Indicates if there is a property with the specified name.
+     * @param name The name of the property to check
+     * @return {@code true} if there is a property of the
+     * specified name, otherwise {@code false}
      */
-    protected Object createIndexedProperty(final String name, final Class<?> type) {
-        // Create the indexed object
-        Object indexedProperty = null;
-
-        if (type == null) {
-
-            indexedProperty = defaultIndexedProperty(name);
-
-        } else if (type.isArray()) {
-
-            indexedProperty = Array.newInstance(type.getComponentType(), 0);
-
-        } else if (List.class.isAssignableFrom(type)) {
-            if (type.isInterface()) {
-                indexedProperty = defaultIndexedProperty(name);
-            } else {
-                try {
-                    indexedProperty = type.newInstance();
-                }
-                catch (final Exception ex) {
-                    throw new IllegalArgumentException
-                        ("Error instantiating indexed property of type '" +
-                                   type.getName() + "' for '" + name + "' " + ex);
-                }
-            }
-        } else {
+    protected boolean isDynaProperty(final String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("No property name specified");
+        }
 
-            throw new IllegalArgumentException
-                    ("Non-indexed property of type '" + type.getName() + "' for '" + name + "'");
+        // Handle LazyDynaClasses
+        if (dynaClass instanceof LazyDynaClass) {
+            return ((LazyDynaClass)dynaClass).isDynaProperty(name);
         }
 
-        return indexedProperty;
+        // Handle other MutableDynaClass
+        return dynaClass.getDynaProperty(name) != null;
     }
 
     /**
-     * Create a new Instance of a 'Mapped' Property
-     * @param name The name of the property
-     * @param type The class of the property
-     * @return The new value
+     * <p>Returns the {@code Log}.
      */
-    protected Object createMappedProperty(final String name, final Class<?> type) {
-        // Create the mapped object
-        Object mappedProperty = null;
-
-        if ((type == null) || type.isInterface()) {
-
-            mappedProperty = defaultMappedProperty(name);
-
-        } else if (Map.class.isAssignableFrom(type)) {
-            try {
-                mappedProperty = type.newInstance();
-            }
-            catch (final Exception ex) {
-                throw new IllegalArgumentException
-                    ("Error instantiating mapped property of type '" +
-                            type.getName() + "' for '" + name + "' " + ex);
-            }
-        } else {
-
-            throw new IllegalArgumentException
-                    ("Non-mapped property of type '" + type.getName() + "' for '" + name + "'");
+    private Log logger() {
+        if (LOG == null) {
+            LOG = LogFactory.getLog(LazyDynaBean.class);
         }
-
-        return mappedProperty;
+        return LOG;
     }
 
     /**
-     * Create a new Instance of a 'DynaBean' Property.
-     * @param name The name of the property
-     * @param type The class of the property
-     * @return The new value
+     * <p>Creates a new instance of the {@code Map}.</p>
+     * @return a new Map instance
      */
-    protected Object createDynaBeanProperty(final String name, final Class<?> type) {
-        try {
-            return type.newInstance();
-        }
-        catch (final Exception ex) {
-            if (logger().isWarnEnabled()) {
-                logger().warn("Error instantiating DynaBean property of type '" +
-                        type.getName() + "' for '" + name + "' ", ex);
-            }
-            return null;
-        }
+    protected Map<String, Object> newMap() {
+        return new HashMap<>();
     }
 
     /**
-     * Create a new Instance of a 'Primitive' Property.
-     * @param name The name of the property
-     * @param type The class of the property
-     * @return The new value
+     * Remove any existing value for the specified key on the
+     * specified mapped property.
+     *
+     * @param name Name of the property for which a value is to
+     *  be removed
+     * @param key Key of the value to be removed
+     *
+     * @throws IllegalArgumentException if there is no property
+     *  of the specified name
      */
-    protected Object createPrimitiveProperty(final String name, final Class<?> type) {
-        if (type == Boolean.TYPE) {
-            return Boolean.FALSE;
-        }
-        if (type == Integer.TYPE) {
-            return Integer_ZERO;
-        }
-        if (type == Long.TYPE) {
-            return Long_ZERO;
-        }
-        if (type == Double.TYPE) {
-            return Double_ZERO;
-        }
-        if (type == Float.TYPE) {
-            return Float_ZERO;
-        }
-        if (type == Byte.TYPE) {
-            return Byte_ZERO;
-        }
-        if (type == Short.TYPE) {
-            return Short_ZERO;
+    @Override
+    public void remove(final String name, final String key) {
+        if (name == null) {
+            throw new IllegalArgumentException("No property name specified");
         }
-        if (type == Character.TYPE) {
-            return Character_SPACE;
+
+        final Object value = values.get(name);
+        if (value == null) {
+            return;
         }
-        return null;
-    }
 
-    /**
-     * Create a new Instance of a {@code java.lang.Number} Property.
-     * @param name The name of the property
-     * @param type The class of the property
-     * @return The new value
-     */
-    protected Object createNumberProperty(final String name, final Class<?> type) {
-        return null;
+        if (!(value instanceof Map)) {
+            throw new IllegalArgumentException
+                    ("Non-mapped property for '" + name + "(" + key + ")'"
+                            + value.getClass().getName());
+        }
+        ((Map<?, ?>) value).remove(key);
     }
 
     /**
-     * Create a new Instance of other Property types
-     * @param name The name of the property
-     * @param type The class of the property
-     * @return The new value
+     * Sets the value of an indexed property with the specified name.
+     *
+     * @param name Name of the property whose value is to be set
+     * @param index Index of the property to be set
+     * @param value Value to which this property is to be set
+     *
+     * @throws ConversionException if the specified value cannot be
+     *  converted to the type required for this property
+     * @throws IllegalArgumentException if there is no property
+     *  of the specified name
+     * @throws IllegalArgumentException if the specified property
+     *  exists, but is not indexed
+     * @throws IndexOutOfBoundsException if the specified index
+     *  is outside the range of the underlying property
      */
-    protected Object createOtherProperty(final String name, final Class<?> type) {
-        if (type == Object.class    ||
-            type == String.class    ||
-            type == Boolean.class   ||
-            type == Character.class ||
-            Date.class.isAssignableFrom(type)) {
+    @Override
+    public void set(final String name, final int index, final Object value) {
+        // If its not a property, then create default indexed property
+        if (!isDynaProperty(name)) {
+            set(name, defaultIndexedProperty(name));
+        }
 
-            return null;
+        // Get the indexed property
+        Object indexedProperty = get(name);
 
+        // Check that the property is indexed
+        if (!dynaClass.getDynaProperty(name).isIndexed()) {
+            throw new IllegalArgumentException
+                ("Non-indexed property for '" + name + "[" + index + "]'"
+                            + dynaClass.getDynaProperty(name).getType().getName());
         }
 
-        try {
-            return type.newInstance();
-        }
-        catch (final Exception ex) {
-            if (logger().isWarnEnabled()) {
-                logger().warn("Error instantiating property of type '" + type.getName() + "' for '" + name + "' ", ex);
-            }
-            return null;
+        // Grow indexed property to appropriate size
+        indexedProperty = growIndexedProperty(name, indexedProperty, index);
+
+        // Set the value in an array
+        if (indexedProperty.getClass().isArray()) {
+            Array.set(indexedProperty, index, value);
+        } else if (indexedProperty instanceof List) {
+            @SuppressWarnings("unchecked")
+            final
+            // Indexed properties are stored in a List<Object>
+            List<Object> values = (List<Object>) indexedProperty;
+            values.set(index, value);
+        } else {
+            throw new IllegalArgumentException
+                ("Non-indexed property for '" + name + "[" + index + "]' "
+                            + indexedProperty.getClass().getName());
         }
     }
 
     /**
-     * <p>Creates a new {@code ArrayList} for an 'indexed' property
-     *    which doesn't exist.</p>
+     * Sets the value of a simple property with the specified name.
      *
-     * <p>This method should be overridden if an alternative {@code List}
-     *    or {@code Array} implementation is required for 'indexed' properties.</p>
+     * @param name Name of the property whose value is to be set
+     * @param value Value to which this property is to be set
      *
-     * @param name Name of the 'indexed property.
-     * @return The default value for an indexed property (java.util.ArrayList)
+     * @throws IllegalArgumentException if this is not an existing property
+     *  name for our DynaClass and the MutableDynaClass is restricted
+     * @throws ConversionException if the specified value cannot be
+     *  converted to the type required for this property
+     * @throws NullPointerException if an attempt is made to set a
+     *  primitive property to null
      */
-    protected Object defaultIndexedProperty(final String name) {
-        return new ArrayList<>();
+    @Override
+    public void set(final String name, final Object value) {
+        // If the property doesn't exist, then add it
+        if (!isDynaProperty(name)) {
+
+            if (dynaClass.isRestricted()) {
+                throw new IllegalArgumentException
+                    ("Invalid property name '" + name + "' (DynaClass is restricted)");
+            }
+            if (value == null) {
+                dynaClass.add(name);
+            } else {
+                dynaClass.add(name, value.getClass());
+            }
+
+        }
+
+        final DynaProperty descriptor = dynaClass.getDynaProperty(name);
+
+        if (value == null) {
+            if (descriptor.getType().isPrimitive()) {
+                throw new NullPointerException
+                        ("Primitive value for '" + name + "'");
+            }
+        } else if (!isAssignable(descriptor.getType(), value.getClass())) {
+            throw ConversionException.format
+                    ("Cannot assign value of type '%s' to property '%s' of type '%s'", value.getClass().getName(), name, descriptor.getType().getName());
+        }
+
+        // Set the property's value
+        values.put(name, value);
     }
 
     /**
-     * <p>Creates a new {@code HashMap} for a 'mapped' property
-     *    which doesn't exist.</p>
+     * Sets the value of a mapped property with the specified name.
      *
-     * <p>This method can be overridden if an alternative {@code Map}
-     *    implementation is required for 'mapped' properties.</p>
+     * @param name Name of the property whose value is to be set
+     * @param key Key of the property to be set
+     * @param value Value to which this property is to be set
      *
-     * @param name Name of the 'mapped property.
-     * @return The default value for a mapped property (java.util.HashMap)
-     */
-    protected Map<String, Object> defaultMappedProperty(final String name) {
-        return new HashMap<>();
-    }
-
-    /**
-     * Indicates if there is a property with the specified name.
-     * @param name The name of the property to check
-     * @return {@code true} if there is a property of the
-     * specified name, otherwise {@code false}
+     * @throws ConversionException if the specified value cannot be
+     *  converted to the type required for this property
+     * @throws IllegalArgumentException if there is no property
+     *  of the specified name
+     * @throws IllegalArgumentException if the specified property
+     *  exists, but is not mapped
      */
-    protected boolean isDynaProperty(final String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("No property name specified");
+    @Override
+    public void set(final String name, final String key, final Object value) {
+        // If the 'mapped' property doesn't exist, then add it
+        if (!isDynaProperty(name)) {
+            set(name, defaultMappedProperty(name));
         }
 
-        // Handle LazyDynaClasses
-        if (dynaClass instanceof LazyDynaClass) {
-            return ((LazyDynaClass)dynaClass).isDynaProperty(name);
+        // Get the mapped property
+        final Object mappedProperty = get(name);
+
+        // Check that the property is mapped
+        if (!dynaClass.getDynaProperty(name).isMapped()) {
+            throw new IllegalArgumentException
+                ("Non-mapped property for '" + name + "(" + key + ")'"
+                            + dynaClass.getDynaProperty(name).getType().getName());
         }
 
-        // Handle other MutableDynaClass
-        return dynaClass.getDynaProperty(name) != null;
+        // Set the value in the Map
+        @SuppressWarnings("unchecked")
+        final
+        // mapped properties are stored in a Map<String, Object>
+        Map<String, Object> valuesMap = (Map<String, Object>) mappedProperty;
+        valuesMap.put(key, value);
     }
 
     /**
-     * Is an object of the source class assignable to the destination class?
+     * <p>Return the size of an indexed or mapped property.</p>
      *
-     * @param dest Destination class
-     * @param source Source class
-     * @return {@code true} if the source class is assignable to the
-     * destination class, otherwise {@code false}
+     * @param name Name of the property
+     * @return The indexed or mapped property size
+     * @throws IllegalArgumentException if no property name is specified
      */
-    protected boolean isAssignable(final Class<?> dest, final Class<?> source) {
-        if (dest.isAssignableFrom(source) ||
-                dest == Boolean.TYPE && source == Boolean.class ||
-                dest == Byte.TYPE && source == Byte.class ||
-                dest == Character.TYPE && source == Character.class ||
-                dest == Double.TYPE && source == Double.class ||
-                dest == Float.TYPE && source == Float.class ||
-                dest == Integer.TYPE && source == Integer.class ||
-                dest == Long.TYPE && source == Long.class ||
-                dest == Short.TYPE && source == Short.class) {
-            return true;
+    public int size(final String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("No property name specified");
         }
-        return false;
 
-    }
+        final Object value = values.get(name);
+        if (value == null) {
+            return 0;
+        }
 
-    /**
-     * <p>Creates a new instance of the {@code Map}.</p>
-     * @return a new Map instance
-     */
-    protected Map<String, Object> newMap() {
-        return new HashMap<>();
-    }
+        if (value instanceof Map) {
+            return ((Map<?, ?>)value).size();
+        }
 
-    /**
-     * <p>Returns the {@code Log}.
-     */
-    private Log logger() {
-        if (LOG == null) {
-            LOG = LogFactory.getLog(LazyDynaBean.class);
+        if (value instanceof List) {
+            return ((List<?>)value).size();
         }
-        return LOG;
+
+        if (value.getClass().isArray()) {
+            return Array.getLength(value);
+        }
+
+        return 0;
     }
 
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/LazyDynaClass.java b/src/main/java/org/apache/commons/beanutils2/LazyDynaClass.java
index 2635e25f..bba98f85 100644
--- a/src/main/java/org/apache/commons/beanutils2/LazyDynaClass.java
+++ b/src/main/java/org/apache/commons/beanutils2/LazyDynaClass.java
@@ -86,16 +86,6 @@ public class LazyDynaClass extends BasicDynaClass implements MutableDynaClass  {
         this(name, dynaBeanClass, null);
     }
 
-    /**
-     * Constructs a new LazyDynaClass with the specified name and properties.
-     *
-     * @param name Name of this DynaBean class
-     * @param properties Property descriptors for the supported properties
-     */
-    public LazyDynaClass(final String name, final DynaProperty[] properties) {
-        this(name, LazyDynaBean.class, properties);
-    }
-
     /**
      * Constructs a new LazyDynaClass with the specified name, DynaBean class and properties.
      *
@@ -108,52 +98,45 @@ public class LazyDynaClass extends BasicDynaClass implements MutableDynaClass  {
     }
 
     /**
-     * <p>Is this DynaClass currently restricted.</p>
-     * <p>If restricted, no changes to the existing registration of
-     *  property names, data types, readability, or writeability are allowed.</p>
-     * @return {@code true} if this {@link MutableDynaClass} cannot be changed
-     * otherwise {@code false}
-     */
-    @Override
-    public boolean isRestricted() {
-        return restricted;
-    }
-
-    /**
-     * <p>Set whether this DynaClass is currently restricted.</p>
-     * <p>If restricted, no changes to the existing registration of
-     *  property names, data types, readability, or writeability are allowed.</p>
-     * @param restricted {@code true} if this {@link MutableDynaClass} cannot
-     * be changed otherwise {@code false}
+     * Constructs a new LazyDynaClass with the specified name and properties.
+     *
+     * @param name Name of this DynaBean class
+     * @param properties Property descriptors for the supported properties
      */
-    @Override
-    public void setRestricted(final boolean restricted) {
-        this.restricted = restricted;
+    public LazyDynaClass(final String name, final DynaProperty[] properties) {
+        this(name, LazyDynaBean.class, properties);
     }
 
     /**
-     * Should this DynaClass return a {@code null} from
-     * the {@code getDynaProperty(name)} method if the property
-     * doesn't exist.
+     * Add a new dynamic property.
      *
-     * @return {@code true</code> if a <code>null} {@link DynaProperty}
-     * should be returned if the property doesn't exist, otherwise
-     * {@code false} if a new {@link DynaProperty} should be created.
+     * @param property Property the new dynamic property to add.
+     *
+     * @throws IllegalArgumentException if name is null
+     * @throws IllegalStateException if this DynaClass is currently
+     *  restricted, so no new properties can be added
      */
-    public boolean isReturnNull() {
-        return returnNull;
-    }
+    protected void add(final DynaProperty property) {
+        if (property.getName() == null) {
+            throw new IllegalArgumentException("Property name is missing.");
+        }
 
-    /**
-     * Sets whether this DynaClass should return a {@code null} from
-     * the {@code getDynaProperty(name)} method if the property
-     * doesn't exist.
-     * @param returnNull {@code true</code> if a <code>null} {@link DynaProperty}
-     * should be returned if the property doesn't exist, otherwise
-     * {@code false} if a new {@link DynaProperty} should be created.
-     */
-    public void setReturnNull(final boolean returnNull) {
-        this.returnNull = returnNull;
+        if (isRestricted()) {
+            throw new IllegalStateException("DynaClass is currently restricted. No new properties can be added.");
+        }
+
+        // Check if property already exists
+        if (propertiesMap.get(property.getName()) != null) {
+            return;
+        }
+
+        // Create a new property array with the specified property
+        final DynaProperty[] oldProperties = getDynaProperties();
+        final DynaProperty[] newProperties = Arrays.copyOf(oldProperties, oldProperties.length + 1);
+        newProperties[oldProperties.length] = property;
+
+        // Update the properties
+        setProperties(newProperties);
     }
 
     /**
@@ -219,35 +202,89 @@ public class LazyDynaClass extends BasicDynaClass implements MutableDynaClass  {
     }
 
     /**
-     * Add a new dynamic property.
+     * <p>Return a property descriptor for the specified property.</p>
      *
-     * @param property Property the new dynamic property to add.
+     * <p>If the property is not found and the {@code returnNull} indicator is
+     *    {@code true</code>, this method always returns <code>null}.</p>
      *
-     * @throws IllegalArgumentException if name is null
-     * @throws IllegalStateException if this DynaClass is currently
-     *  restricted, so no new properties can be added
+     * <p>If the property is not found and the {@code returnNull} indicator is
+     *    {@code false} a new property descriptor is created and returned (although
+     *    its not actually added to the DynaClass's properties). This is the default
+     *    behavior.</p>
+     *
+     * <p>The reason for not returning a {@code null} property descriptor is that
+     *    {@code BeanUtils} uses this method to check if a property exists
+     *    before trying to set it - since these <i>Lazy</i> implementations automatically
+     *    add any new properties when they are set, returning {@code null} from
+     *    this method would defeat their purpose.</p>
+     *
+     * @param name Name of the dynamic property for which a descriptor
+     *  is requested
+     * @return The dyna property for the specified name
+     *
+     * @throws IllegalArgumentException if no property name is specified
      */
-    protected void add(final DynaProperty property) {
-        if (property.getName() == null) {
+    @Override
+    public DynaProperty getDynaProperty(final String name) {
+        if (name == null) {
             throw new IllegalArgumentException("Property name is missing.");
         }
 
-        if (isRestricted()) {
-            throw new IllegalStateException("DynaClass is currently restricted. No new properties can be added.");
+        DynaProperty dynaProperty = propertiesMap.get(name);
+
+        // If it doesn't exist and returnNull is false
+        // create a new DynaProperty
+        if (dynaProperty == null && !isReturnNull() && !isRestricted()) {
+            dynaProperty = new DynaProperty(name);
         }
 
-        // Check if property already exists
-        if (propertiesMap.get(property.getName()) != null) {
-            return;
+        return dynaProperty;
+    }
+
+    /**
+     * <p>Indicate whether a property actually exists.</p>
+     *
+     * <p><strong>N.B.</strong> Using {@code getDynaProperty(name) == null}
+     * doesn't work in this implementation because that method might
+     * return a DynaProperty if it doesn't exist (depending on the
+     * {@code returnNull} indicator).</p>
+     *
+     * @param name The name of the property to check
+     * @return {@code true} if there is a property of the
+     * specified name, otherwise {@code false}
+     * @throws IllegalArgumentException if no property name is specified
+     */
+    public boolean isDynaProperty(final String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("Property name is missing.");
         }
 
-        // Create a new property array with the specified property
-        final DynaProperty[] oldProperties = getDynaProperties();
-        final DynaProperty[] newProperties = Arrays.copyOf(oldProperties, oldProperties.length + 1);
-        newProperties[oldProperties.length] = property;
+        return propertiesMap.get(name) != null;
+    }
 
-        // Update the properties
-        setProperties(newProperties);
+    /**
+     * <p>Is this DynaClass currently restricted.</p>
+     * <p>If restricted, no changes to the existing registration of
+     *  property names, data types, readability, or writeability are allowed.</p>
+     * @return {@code true} if this {@link MutableDynaClass} cannot be changed
+     * otherwise {@code false}
+     */
+    @Override
+    public boolean isRestricted() {
+        return restricted;
+    }
+
+    /**
+     * Should this DynaClass return a {@code null} from
+     * the {@code getDynaProperty(name)} method if the property
+     * doesn't exist.
+     *
+     * @return {@code true</code> if a <code>null} {@link DynaProperty}
+     * should be returned if the property doesn't exist, otherwise
+     * {@code false} if a new {@link DynaProperty} should be created.
+     */
+    public boolean isReturnNull() {
+        return returnNull;
     }
 
     /**
@@ -294,64 +331,27 @@ public class LazyDynaClass extends BasicDynaClass implements MutableDynaClass  {
     }
 
     /**
-     * <p>Return a property descriptor for the specified property.</p>
-     *
-     * <p>If the property is not found and the {@code returnNull} indicator is
-     *    {@code true</code>, this method always returns <code>null}.</p>
-     *
-     * <p>If the property is not found and the {@code returnNull} indicator is
-     *    {@code false} a new property descriptor is created and returned (although
-     *    its not actually added to the DynaClass's properties). This is the default
-     *    behavior.</p>
-     *
-     * <p>The reason for not returning a {@code null} property descriptor is that
-     *    {@code BeanUtils} uses this method to check if a property exists
-     *    before trying to set it - since these <i>Lazy</i> implementations automatically
-     *    add any new properties when they are set, returning {@code null} from
-     *    this method would defeat their purpose.</p>
-     *
-     * @param name Name of the dynamic property for which a descriptor
-     *  is requested
-     * @return The dyna property for the specified name
-     *
-     * @throws IllegalArgumentException if no property name is specified
+     * <p>Set whether this DynaClass is currently restricted.</p>
+     * <p>If restricted, no changes to the existing registration of
+     *  property names, data types, readability, or writeability are allowed.</p>
+     * @param restricted {@code true} if this {@link MutableDynaClass} cannot
+     * be changed otherwise {@code false}
      */
     @Override
-    public DynaProperty getDynaProperty(final String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("Property name is missing.");
-        }
-
-        DynaProperty dynaProperty = propertiesMap.get(name);
-
-        // If it doesn't exist and returnNull is false
-        // create a new DynaProperty
-        if (dynaProperty == null && !isReturnNull() && !isRestricted()) {
-            dynaProperty = new DynaProperty(name);
-        }
-
-        return dynaProperty;
+    public void setRestricted(final boolean restricted) {
+        this.restricted = restricted;
     }
 
     /**
-     * <p>Indicate whether a property actually exists.</p>
-     *
-     * <p><strong>N.B.</strong> Using {@code getDynaProperty(name) == null}
-     * doesn't work in this implementation because that method might
-     * return a DynaProperty if it doesn't exist (depending on the
-     * {@code returnNull} indicator).</p>
-     *
-     * @param name The name of the property to check
-     * @return {@code true} if there is a property of the
-     * specified name, otherwise {@code false}
-     * @throws IllegalArgumentException if no property name is specified
+     * Sets whether this DynaClass should return a {@code null} from
+     * the {@code getDynaProperty(name)} method if the property
+     * doesn't exist.
+     * @param returnNull {@code true</code> if a <code>null} {@link DynaProperty}
+     * should be returned if the property doesn't exist, otherwise
+     * {@code false} if a new {@link DynaProperty} should be created.
      */
-    public boolean isDynaProperty(final String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("Property name is missing.");
-        }
-
-        return propertiesMap.get(name) != null;
+    public void setReturnNull(final boolean returnNull) {
+        this.returnNull = returnNull;
     }
 
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/beanutils2/LazyDynaList.java b/src/main/java/org/apache/commons/beanutils2/LazyDynaList.java
index 3b67bef3..cdc2f754 100644
--- a/src/main/java/org/apache/commons/beanutils2/LazyDynaList.java
+++ b/src/main/java/org/apache/commons/beanutils2/LazyDynaList.java
@@ -194,14 +194,24 @@ public class LazyDynaList extends ArrayList<Object> {
     }
 
     /**
-     * Constructs a LazyDynaList with the
-     * specified capacity.
+     * Constructs a  LazyDynaList with a
+     * specified type for its elements.
      *
-     * @param capacity The initial capacity of the list.
+     * @param elementType The Type of the List's elements.
      */
-    public LazyDynaList(final int capacity) {
-        super(capacity);
+    public LazyDynaList(final Class<?> elementType) {
+        setElementType(elementType);
+    }
 
+    /**
+     * Constructs a  LazyDynaList populated with the
+     * elements of a Collection.
+     *
+     * @param collection The Collection to populate the List from.
+     */
+    public LazyDynaList(final Collection<?> collection) {
+        super(collection.size());
+        addAll(collection);
     }
 
     /**
@@ -215,24 +225,14 @@ public class LazyDynaList extends ArrayList<Object> {
     }
 
     /**
-     * Constructs a  LazyDynaList with a
-     * specified type for its elements.
+     * Constructs a LazyDynaList with the
+     * specified capacity.
      *
-     * @param elementType The Type of the List's elements.
+     * @param capacity The initial capacity of the list.
      */
-    public LazyDynaList(final Class<?> elementType) {
-        setElementType(elementType);
-    }
+    public LazyDynaList(final int capacity) {
+        super(capacity);
 
-    /**
-     * Constructs a  LazyDynaList populated with the
-     * elements of a Collection.
-     *
-     * @param collection The Collection to populate the List from.
-     */
-    public LazyDynaList(final Collection<?> collection) {
-        super(collection.size());
-        addAll(collection);
     }
 
     /**
@@ -334,6 +334,20 @@ public class LazyDynaList extends ArrayList<Object> {
         return true;
     }
 
+    /**
+     * Creates a new {@code LazyDynaMap} object for the given property value.
+     *
+     * @param value the property value
+     * @return the newly created {@code LazyDynaMap}
+     */
+    private LazyDynaMap createDynaBeanForMapProperty(final Object value) {
+        @SuppressWarnings("unchecked")
+        final
+        // map properties are always stored as Map<String, Object>
+        Map<String, Object> valueMap = (Map<String, Object>) value;
+        return new LazyDynaMap(valueMap);
+    }
+
     /**
      * <p>Return the element at the specified position.</p>
      *
@@ -351,6 +365,35 @@ public class LazyDynaList extends ArrayList<Object> {
         return super.get(index);
     }
 
+    /**
+     * Gets the DynaClass.
+     */
+    private DynaClass getDynaClass() {
+        return elementDynaClass == null ? wrapDynaClass : elementDynaClass;
+    }
+
+    /**
+     * <p>Automatically <i>grown</i> the List
+     *    to the appropriate size, populating with
+     *    DynaBeans.</p>
+     *
+     * @param requiredSize the required size of the List.
+     */
+    private void growList(final int requiredSize) {
+        if (requiredSize < size()) {
+            return;
+        }
+
+        ensureCapacity(requiredSize + 1);
+
+        for (int i = size(); i < requiredSize; i++) {
+            final DynaBean dynaBean = transform(null);
+            super.add(dynaBean);
+        }
+    }
+
+
+
     /**
      * <p>Set the element at the specified position.</p>
      *
@@ -371,6 +414,96 @@ public class LazyDynaList extends ArrayList<Object> {
         return super.set(index, dynaBean);
     }
 
+    /**
+     * <p>Set the element Type and DynaClass.</p>
+     *
+     * @param elementDynaClass The DynaClass of the elements.
+     * @throws IllegalArgumentException if the List already
+     *            contains elements or the DynaClass is null.
+     */
+    public void setElementDynaClass(final DynaClass elementDynaClass) {
+        if (elementDynaClass == null) {
+            throw new IllegalArgumentException("Element DynaClass is missing");
+        }
+
+        if (!isEmpty()) {
+            throw new IllegalStateException("Element DynaClass cannot be reset");
+        }
+
+        // Try to create a new instance of the DynaBean
+        try {
+            final DynaBean dynaBean  = elementDynaClass.newInstance();
+            this.elementDynaBeanType = dynaBean.getClass();
+            if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType)) {
+                this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
+                this.wrapDynaClass = (WrapDynaClass)elementDynaClass;
+            } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType)) {
+                this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass();
+                this.elementDynaClass = elementDynaClass;
+            } else {
+                this.elementType = dynaBean.getClass();
+                this.elementDynaClass = elementDynaClass;
+            }
+        } catch (final Exception e) {
+            throw new IllegalArgumentException(
+                        "Error creating DynaBean from " +
+                        elementDynaClass.getClass().getName() + " - " + e);
+        }
+    }
+
+    /**
+     * <p>Set the element Type and DynaClass.</p>
+     *
+     * @param elementType The type of the elements.
+     * @throws IllegalArgumentException if the List already
+     *            contains elements or the DynaClass is null.
+     */
+    public void setElementType(final Class<?> elementType) {
+        if (elementType == null) {
+            throw new IllegalArgumentException("Element Type is missing");
+        }
+
+        final boolean changeType = this.elementType != null && !this.elementType.equals(elementType);
+        if (changeType && !isEmpty()) {
+            throw new IllegalStateException("Element Type cannot be reset");
+        }
+
+        this.elementType = elementType;
+
+        // Create a new object of the specified type
+        Object object = null;
+        try {
+            object = elementType.newInstance();
+        } catch (final Exception e) {
+            throw new IllegalArgumentException("Error creating type: "
+                           + elementType.getName() + " - " + e);
+        }
+
+        // Create a DynaBean
+        DynaBean dynaBean = null;
+        if (Map.class.isAssignableFrom(elementType)) {
+            dynaBean = createDynaBeanForMapProperty(object);
+            this.elementDynaClass = dynaBean.getDynaClass();
+        } else if (DynaBean.class.isAssignableFrom(elementType)) {
+            dynaBean = (DynaBean)object;
+            this.elementDynaClass = dynaBean.getDynaClass();
+        } else {
+            dynaBean = new WrapDynaBean(object);
+            this.wrapDynaClass = (WrapDynaClass)dynaBean.getDynaClass();
+        }
+
+        this.elementDynaBeanType = dynaBean.getClass();
+
+        // Re-calculate the type
+        if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType )) {
+            this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
+        } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType )) {
+            this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass();
+        }
+    }
+
+
+
     /**
      * <p>Converts the List to an Array.</p>
      *
@@ -452,8 +585,6 @@ public class LazyDynaList extends ArrayList<Object> {
                   + elementType.getName());
     }
 
-
-
     /**
      * <p>Converts the List to an DynaBean Array.</p>
      *
@@ -471,116 +602,6 @@ public class LazyDynaList extends ArrayList<Object> {
         return array;
     }
 
-    /**
-     * <p>Set the element Type and DynaClass.</p>
-     *
-     * @param elementType The type of the elements.
-     * @throws IllegalArgumentException if the List already
-     *            contains elements or the DynaClass is null.
-     */
-    public void setElementType(final Class<?> elementType) {
-        if (elementType == null) {
-            throw new IllegalArgumentException("Element Type is missing");
-        }
-
-        final boolean changeType = this.elementType != null && !this.elementType.equals(elementType);
-        if (changeType && !isEmpty()) {
-            throw new IllegalStateException("Element Type cannot be reset");
-        }
-
-        this.elementType = elementType;
-
-        // Create a new object of the specified type
-        Object object = null;
-        try {
-            object = elementType.newInstance();
-        } catch (final Exception e) {
-            throw new IllegalArgumentException("Error creating type: "
-                           + elementType.getName() + " - " + e);
-        }
-
-        // Create a DynaBean
-        DynaBean dynaBean = null;
-        if (Map.class.isAssignableFrom(elementType)) {
-            dynaBean = createDynaBeanForMapProperty(object);
-            this.elementDynaClass = dynaBean.getDynaClass();
-        } else if (DynaBean.class.isAssignableFrom(elementType)) {
-            dynaBean = (DynaBean)object;
-            this.elementDynaClass = dynaBean.getDynaClass();
-        } else {
-            dynaBean = new WrapDynaBean(object);
-            this.wrapDynaClass = (WrapDynaClass)dynaBean.getDynaClass();
-        }
-
-        this.elementDynaBeanType = dynaBean.getClass();
-
-        // Re-calculate the type
-        if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType )) {
-            this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
-        } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType )) {
-            this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass();
-        }
-    }
-
-    /**
-     * <p>Set the element Type and DynaClass.</p>
-     *
-     * @param elementDynaClass The DynaClass of the elements.
-     * @throws IllegalArgumentException if the List already
-     *            contains elements or the DynaClass is null.
-     */
-    public void setElementDynaClass(final DynaClass elementDynaClass) {
-        if (elementDynaClass == null) {
-            throw new IllegalArgumentException("Element DynaClass is missing");
-        }
-
-        if (!isEmpty()) {
-            throw new IllegalStateException("Element DynaClass cannot be reset");
-        }
-
-        // Try to create a new instance of the DynaBean
-        try {
-            final DynaBean dynaBean  = elementDynaClass.newInstance();
-            this.elementDynaBeanType = dynaBean.getClass();
-            if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType)) {
-                this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
-                this.wrapDynaClass = (WrapDynaClass)elementDynaClass;
-            } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType)) {
-                this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass();
-                this.elementDynaClass = elementDynaClass;
-            } else {
-                this.elementType = dynaBean.getClass();
-                this.elementDynaClass = elementDynaClass;
-            }
-        } catch (final Exception e) {
-            throw new IllegalArgumentException(
-                        "Error creating DynaBean from " +
-                        elementDynaClass.getClass().getName() + " - " + e);
-        }
-    }
-
-
-
-    /**
-     * <p>Automatically <i>grown</i> the List
-     *    to the appropriate size, populating with
-     *    DynaBeans.</p>
-     *
-     * @param requiredSize the required size of the List.
-     */
-    private void growList(final int requiredSize) {
-        if (requiredSize < size()) {
-            return;
-        }
-
-        ensureCapacity(requiredSize + 1);
-
-        for (int i = size(); i < requiredSize; i++) {
-            final DynaBean dynaBean = transform(null);
-            super.add(dynaBean);
-        }
-    }
-
     /**
      * <p>Transform the element into a DynaBean:</p>
      *
@@ -655,25 +676,4 @@ public class LazyDynaList extends ArrayList<Object> {
 
         return dynaBean;
     }
-
-    /**
-     * Creates a new {@code LazyDynaMap} object for the given property value.
-     *
-     * @param value the property value
-     * @return the newly created {@code LazyDynaMap}
-     */
-    private LazyDynaMap createDynaBeanForMapProperty(final Object value) {
-        @SuppressWarnings("unchecked")
-        final
-        // map properties are always stored as Map<String, Object>
-        Map<String, Object> valueMap = (Map<String, Object>) value;
-        return new LazyDynaMap(valueMap);
-    }
-
-    /**
-     * Gets the DynaClass.
-     */
-    private DynaClass getDynaClass() {
-        return elementDynaClass == null ? wrapDynaClass : elementDynaClass;
-    }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/LazyDynaMap.java b/src/main/java/org/apache/commons/beanutils2/LazyDynaMap.java
index b211b12f..3cf95956 100644
--- a/src/main/java/org/apache/commons/beanutils2/LazyDynaMap.java
+++ b/src/main/java/org/apache/commons/beanutils2/LazyDynaMap.java
@@ -75,42 +75,39 @@ public class LazyDynaMap extends LazyDynaBean implements MutableDynaClass {
     }
 
     /**
-     * Constructs a new {@code LazyDynaMap} with the specified name.
+     * Constructs a new {@code LazyDynaMap} based on an exisiting DynaClass
      *
-     * @param name Name of this DynaBean class
+     * @param dynaClass DynaClass to copy the name and properties from
      */
-    public LazyDynaMap(final String name) {
-        this(name, (Map<String, Object>)null);
+    public LazyDynaMap(final DynaClass dynaClass) {
+        this(dynaClass.getName(), dynaClass.getDynaProperties());
     }
 
     /**
-     * Constructs a new {@code LazyDynaMap</code> with the specified <code>Map}.
+     * Constructs a new {@code LazyDynaMap} with the specified properties.
      *
-     * @param values The Map backing this {@code LazyDynaMap}
+     * @param properties Property descriptors for the supported properties
      */
-    public LazyDynaMap(final Map<String, Object> values) {
-        this(null, values);
+    public LazyDynaMap(final DynaProperty[] properties) {
+        this(null, properties);
     }
 
     /**
-     * Constructs a new {@code LazyDynaMap</code> with the specified name and  <code>Map}.
+     * Constructs a new {@code LazyDynaMap</code> with the specified <code>Map}.
      *
-     * @param name Name of this DynaBean class
      * @param values The Map backing this {@code LazyDynaMap}
      */
-    public LazyDynaMap(final String name, final Map<String, Object> values) {
-        this.name      = name   == null ? "LazyDynaMap" : name;
-        this.values    = values == null ? newMap()      : values;
-        this.dynaClass = this;
+    public LazyDynaMap(final Map<String, Object> values) {
+        this(null, values);
     }
 
     /**
-     * Constructs a new {@code LazyDynaMap} with the specified properties.
+     * Constructs a new {@code LazyDynaMap} with the specified name.
      *
-     * @param properties Property descriptors for the supported properties
+     * @param name Name of this DynaBean class
      */
-    public LazyDynaMap(final DynaProperty[] properties) {
-        this(null, properties);
+    public LazyDynaMap(final String name) {
+        this(name, (Map<String, Object>)null);
     }
 
     /**
@@ -129,64 +126,125 @@ public class LazyDynaMap extends LazyDynaBean implements MutableDynaClass {
     }
 
     /**
-     * Constructs a new {@code LazyDynaMap} based on an exisiting DynaClass
+     * Constructs a new {@code LazyDynaMap</code> with the specified name and  <code>Map}.
      *
-     * @param dynaClass DynaClass to copy the name and properties from
+     * @param name Name of this DynaBean class
+     * @param values The Map backing this {@code LazyDynaMap}
      */
-    public LazyDynaMap(final DynaClass dynaClass) {
-        this(dynaClass.getName(), dynaClass.getDynaProperties());
+    public LazyDynaMap(final String name, final Map<String, Object> values) {
+        this.name      = name   == null ? "LazyDynaMap" : name;
+        this.values    = values == null ? newMap()      : values;
+        this.dynaClass = this;
     }
 
 
 
     /**
-     * Sets the Map backing this {@code DynaBean}
+     * Add a new dynamic property.
      *
-     * @param values The new Map of values
+     * @param property Property the new dynamic property to add.
+     *
+     * @throws IllegalArgumentException if name is null
      */
-    public void setMap(final Map<String, Object> values) {
-        this.values = values;
+    protected void add(final DynaProperty property) {
+        add(property.getName(), property.getType());
     }
 
     /**
-     * Gets the underlying Map backing this {@code DynaBean}
-     * @return the underlying Map
-     * @since 1.8.0
+     * Add a new dynamic property with no restrictions on data type,
+     * readability, or writeability.
+     *
+     * @param name Name of the new dynamic property
+     *
+     * @throws IllegalArgumentException if name is null
      */
     @Override
-    public Map<String, Object> getMap() {
-        return values;
+    public void add(final String name) {
+        add(name, null);
     }
 
 
 
     /**
-     * Sets the value of a simple property with the specified name.
+     * Add a new dynamic property with the specified data type, but with
+     * no restrictions on readability or writeability.
      *
-     * @param name Name of the property whose value is to be set
-     * @param value Value to which this property is to be set
+     * @param name Name of the new dynamic property
+     * @param type Data type of the new dynamic property (null for no
+     *  restrictions)
+     *
+     * @throws IllegalArgumentException if name is null
+     * @throws IllegalStateException if this DynaClass is currently
+     *  restricted, so no new properties can be added
      */
     @Override
-    public void set(final String name, final Object value) {
-        if (isRestricted() && !values.containsKey(name)) {
-            throw new IllegalArgumentException
-                    ("Invalid property name '" + name + "' (DynaClass is restricted)");
+    public void add(final String name, final Class<?> type) {
+        if (name == null) {
+            throw new IllegalArgumentException("Property name is missing.");
         }
 
-        values.put(name, value);
+        if (isRestricted()) {
+            throw new IllegalStateException("DynaClass is currently restricted. No new properties can be added.");
+        }
+
+        final Object value = values.get(name);
+
+        // Check if the property already exists
+        if (value == null) {
+            values.put(name, type == null ? null : createProperty(name, type));
+        }
     }
 
 
 
     /**
-     * Gets the name of this DynaClass (analogous to the
-     * {@code getName()</code> method of <code>java.lang.Class})
+     * <p>Add a new dynamic property with the specified data type, readability,
+     * and writeability.</p>
      *
-     * @return the name of the DynaClass
+     * <p><strong>N.B.</strong>Support for readable/writable properties has not been implemented
+     *    and this method always throws a {@code UnsupportedOperationException}.</p>
+     *
+     * <p>I'm not sure the intention of the original authors for this method, but it seems to
+     *    me that readable/writable should be attributes of the {@code DynaProperty} class
+     *    (which they are not) and is the reason this method has not been implemented.</p>
+     *
+     * @param name Name of the new dynamic property
+     * @param type Data type of the new dynamic property (null for no
+     *  restrictions)
+     * @param readable Set to {@code true} if this property value
+     *  should be readable
+     * @param writable Set to {@code true} if this property value
+     *  should be writable
+     *
+     * @throws UnsupportedOperationException anytime this method is called
      */
     @Override
-    public String getName() {
-        return this.name;
+    public void add(final String name, final Class<?> type, final boolean readable, final boolean writable) {
+        throw new java.lang.UnsupportedOperationException("readable/writable properties not supported");
+    }
+
+    /**
+     * <p>Return an array of {@code PropertyDescriptor} for the properties
+     * currently defined in this DynaClass.  If no properties are defined, a
+     * zero-length array will be returned.</p>
+     *
+     * <p><strong>FIXME</strong> - Should we really be implementing
+     * {@code getBeanInfo()} instead, which returns property descriptors
+     * and a bunch of other stuff?</p>
+     * @return the set of properties for this DynaClass
+     */
+    @Override
+    public DynaProperty[] getDynaProperties() {
+        int i = 0;
+        final DynaProperty[] properties = new DynaProperty[values.size()];
+        for (final Map.Entry<String, Object> e : values.entrySet()) {
+            final String name = e.getKey();
+            final Object value = values.get(name);
+            properties[i++] = new DynaProperty(name, value == null ? null
+                    : value.getClass());
+        }
+
+        return properties;
     }
 
     /**
@@ -233,57 +291,47 @@ public class LazyDynaMap extends LazyDynaBean implements MutableDynaClass {
     }
 
     /**
-     * <p>Return an array of {@code PropertyDescriptor} for the properties
-     * currently defined in this DynaClass.  If no properties are defined, a
-     * zero-length array will be returned.</p>
-     *
-     * <p><strong>FIXME</strong> - Should we really be implementing
-     * {@code getBeanInfo()} instead, which returns property descriptors
-     * and a bunch of other stuff?</p>
-     * @return the set of properties for this DynaClass
+     * Gets the underlying Map backing this {@code DynaBean}
+     * @return the underlying Map
+     * @since 1.8.0
      */
     @Override
-    public DynaProperty[] getDynaProperties() {
-        int i = 0;
-        final DynaProperty[] properties = new DynaProperty[values.size()];
-        for (final Map.Entry<String, Object> e : values.entrySet()) {
-            final String name = e.getKey();
-            final Object value = values.get(name);
-            properties[i++] = new DynaProperty(name, value == null ? null
-                    : value.getClass());
-        }
-
-        return properties;
+    public Map<String, Object> getMap() {
+        return values;
     }
 
     /**
-     * Instantiate and return a new DynaBean instance, associated
-     * with this DynaClass.
-     * @return A new {@code DynaBean} instance
+     * Gets the name of this DynaClass (analogous to the
+     * {@code getName()</code> method of <code>java.lang.Class})
+     *
+     * @return the name of the DynaClass
      */
     @Override
-    public DynaBean newInstance()  {
-        // Create a new instance of the Map
-        Map<String, Object> newMap = null;
-        try {
-            final
-            // The new map is used as properties map
-            Map<String, Object> temp = getMap().getClass().newInstance();
-            newMap = temp;
-        } catch(final Exception ex) {
-            newMap = newMap();
-        }
+    public String getName() {
+        return this.name;
+    }
 
-        // Crate new LazyDynaMap and initialize properties
-        final LazyDynaMap lazyMap = new LazyDynaMap(newMap);
-        final DynaProperty[] properties = this.getDynaProperties();
-        if (properties != null) {
-            for (final DynaProperty property : properties) {
-                lazyMap.add(property);
+    /**
+         * <p>Indicate whether a property actually exists.</p>
+         *
+         * <p><strong>N.B.</strong> Using {@code getDynaProperty(name) == null}
+         * doesn't work in this implementation because that method might
+         * return a DynaProperty if it doesn't exist (depending on the
+         * {@code returnNull} indicator).</p>
+         *
+         * @param name Name of the dynamic property
+         * @return {@code true} if the property exists,
+         * otherwise {@code false}
+         * @throws IllegalArgumentException if no property name is specified
+         */
+        @Override
+        protected boolean isDynaProperty(final String name) {
+            if (name == null) {
+                throw new IllegalArgumentException("Property name is missing.");
             }
+
+            return values.containsKey(name);
         }
-        return lazyMap;
-    }
 
     /**
      * <p>Is this DynaClass currently restricted.</p>
@@ -299,95 +347,45 @@ public class LazyDynaMap extends LazyDynaBean implements MutableDynaClass {
     }
 
     /**
-     * <p>Set whether this DynaClass is currently restricted.</p>
-     * <p>If restricted, no changes to the existing registration of
-     *  property names, data types, readability, or writeability are allowed.</p>
-     *
-     * @param restricted The new restricted state
-     */
-    @Override
-    public void setRestricted(final boolean restricted) {
-        this.restricted = restricted;
-    }
-
-    /**
-     * Add a new dynamic property with no restrictions on data type,
-     * readability, or writeability.
-     *
-     * @param name Name of the new dynamic property
+     * Should this DynaClass return a {@code null} from
+     * the {@code getDynaProperty(name)} method if the property
+     * doesn't exist.
      *
-     * @throws IllegalArgumentException if name is null
+     * @return {@code true</code> if a <code>null} {@link DynaProperty}
+     * should be returned if the property doesn't exist, otherwise
+     * {@code false} if a new {@link DynaProperty} should be created.
      */
-    @Override
-    public void add(final String name) {
-        add(name, null);
+    public boolean isReturnNull() {
+        return returnNull;
     }
 
     /**
-     * Add a new dynamic property with the specified data type, but with
-     * no restrictions on readability or writeability.
-     *
-     * @param name Name of the new dynamic property
-     * @param type Data type of the new dynamic property (null for no
-     *  restrictions)
-     *
-     * @throws IllegalArgumentException if name is null
-     * @throws IllegalStateException if this DynaClass is currently
-     *  restricted, so no new properties can be added
+     * Instantiate and return a new DynaBean instance, associated
+     * with this DynaClass.
+     * @return A new {@code DynaBean} instance
      */
     @Override
-    public void add(final String name, final Class<?> type) {
-        if (name == null) {
-            throw new IllegalArgumentException("Property name is missing.");
-        }
-
-        if (isRestricted()) {
-            throw new IllegalStateException("DynaClass is currently restricted. No new properties can be added.");
+    public DynaBean newInstance()  {
+        // Create a new instance of the Map
+        Map<String, Object> newMap = null;
+        try {
+            final
+            // The new map is used as properties map
+            Map<String, Object> temp = getMap().getClass().newInstance();
+            newMap = temp;
+        } catch(final Exception ex) {
+            newMap = newMap();
         }
 
-        final Object value = values.get(name);
-
-        // Check if the property already exists
-        if (value == null) {
-            values.put(name, type == null ? null : createProperty(name, type));
+        // Crate new LazyDynaMap and initialize properties
+        final LazyDynaMap lazyMap = new LazyDynaMap(newMap);
+        final DynaProperty[] properties = this.getDynaProperties();
+        if (properties != null) {
+            for (final DynaProperty property : properties) {
+                lazyMap.add(property);
+            }
         }
-    }
-
-    /**
-     * <p>Add a new dynamic property with the specified data type, readability,
-     * and writeability.</p>
-     *
-     * <p><strong>N.B.</strong>Support for readable/writable properties has not been implemented
-     *    and this method always throws a {@code UnsupportedOperationException}.</p>
-     *
-     * <p>I'm not sure the intention of the original authors for this method, but it seems to
-     *    me that readable/writable should be attributes of the {@code DynaProperty} class
-     *    (which they are not) and is the reason this method has not been implemented.</p>
-     *
-     * @param name Name of the new dynamic property
-     * @param type Data type of the new dynamic property (null for no
-     *  restrictions)
-     * @param readable Set to {@code true} if this property value
-     *  should be readable
-     * @param writable Set to {@code true} if this property value
-     *  should be writable
-     *
-     * @throws UnsupportedOperationException anytime this method is called
-     */
-    @Override
-    public void add(final String name, final Class<?> type, final boolean readable, final boolean writable) {
-        throw new java.lang.UnsupportedOperationException("readable/writable properties not supported");
-    }
-
-    /**
-     * Add a new dynamic property.
-     *
-     * @param property Property the new dynamic property to add.
-     *
-     * @throws IllegalArgumentException if name is null
-     */
-    protected void add(final DynaProperty property) {
-        add(property.getName(), property.getType());
+        return lazyMap;
     }
 
     /**
@@ -417,51 +415,53 @@ public class LazyDynaMap extends LazyDynaBean implements MutableDynaClass {
     }
 
     /**
-     * Should this DynaClass return a {@code null} from
-     * the {@code getDynaProperty(name)} method if the property
-     * doesn't exist.
+     * Sets the value of a simple property with the specified name.
      *
-     * @return {@code true</code> if a <code>null} {@link DynaProperty}
-     * should be returned if the property doesn't exist, otherwise
-     * {@code false} if a new {@link DynaProperty} should be created.
+     * @param name Name of the property whose value is to be set
+     * @param value Value to which this property is to be set
      */
-    public boolean isReturnNull() {
-        return returnNull;
+    @Override
+    public void set(final String name, final Object value) {
+        if (isRestricted() && !values.containsKey(name)) {
+            throw new IllegalArgumentException
+                    ("Invalid property name '" + name + "' (DynaClass is restricted)");
+        }
+
+        values.put(name, value);
     }
 
     /**
-     * Sets whether this DynaClass should return a {@code null} from
-     * the {@code getDynaProperty(name)} method if the property
-     * doesn't exist.
+     * Sets the Map backing this {@code DynaBean}
      *
-     * @param returnNull {@code true</code> if a <code>null} {@link DynaProperty}
-     * should be returned if the property doesn't exist, otherwise
-     * {@code false} if a new {@link DynaProperty} should be created.
+     * @param values The new Map of values
      */
-    public void setReturnNull(final boolean returnNull) {
-        this.returnNull = returnNull;
+    public void setMap(final Map<String, Object> values) {
+        this.values = values;
     }
 
-   /**
-     * <p>Indicate whether a property actually exists.</p>
-     *
-     * <p><strong>N.B.</strong> Using {@code getDynaProperty(name) == null}
-     * doesn't work in this implementation because that method might
-     * return a DynaProperty if it doesn't exist (depending on the
-     * {@code returnNull} indicator).</p>
+    /**
+     * <p>Set whether this DynaClass is currently restricted.</p>
+     * <p>If restricted, no changes to the existing registration of
+     *  property names, data types, readability, or writeability are allowed.</p>
      *
-     * @param name Name of the dynamic property
-     * @return {@code true} if the property exists,
-     * otherwise {@code false}
-     * @throws IllegalArgumentException if no property name is specified
+     * @param restricted The new restricted state
      */
     @Override
-    protected boolean isDynaProperty(final String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("Property name is missing.");
-        }
-
-        return values.containsKey(name);
+    public void setRestricted(final boolean restricted) {
+        this.restricted = restricted;
     }
 
+   /**
+ * Sets whether this DynaClass should return a {@code null} from
+ * the {@code getDynaProperty(name)} method if the property
+ * doesn't exist.
+ *
+ * @param returnNull {@code true</code> if a <code>null} {@link DynaProperty}
+ * should be returned if the property doesn't exist, otherwise
+ * {@code false} if a new {@link DynaProperty} should be created.
+ */
+public void setReturnNull(final boolean returnNull) {
+    this.returnNull = returnNull;
+}
+
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java b/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java
index 084c679b..8da407ad 100644
--- a/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java
+++ b/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java
@@ -43,6 +43,221 @@ import java.lang.reflect.Modifier;
  */
 public class MappedPropertyDescriptor extends PropertyDescriptor {
 
+    /**
+     * Holds a {@link Method} in a {@link SoftReference} so that it
+     * it doesn't prevent any ClassLoader being garbage collected, but
+     * tries to re-create the method if the method reference has been
+     * released.
+     *
+     * See https://issues.apache.org/jira/browse/BEANUTILS-291
+     */
+    private static class MappedMethodReference {
+        private String className;
+        private String methodName;
+        private Reference<Method> methodRef;
+        private Reference<Class<?>> classRef;
+        private Reference<Class<?>> writeParamTypeRef0;
+        private Reference<Class<?>> writeParamTypeRef1;
+        private String[] writeParamClassNames;
+        MappedMethodReference(final Method m) {
+            if (m != null) {
+                className = m.getDeclaringClass().getName();
+                methodName = m.getName();
+                methodRef = new SoftReference<>(m);
+                classRef = new WeakReference<>(m.getDeclaringClass());
+                final Class<?>[] types = m.getParameterTypes();
+                if (types.length == 2) {
+                    writeParamTypeRef0 = new WeakReference<>(types[0]);
+                    writeParamTypeRef1 = new WeakReference<>(types[1]);
+                    writeParamClassNames = new String[2];
+                    writeParamClassNames[0] = types[0].getName();
+                    writeParamClassNames[1] = types[1].getName();
+                }
+            }
+        }
+        private Method get() {
+            if (methodRef == null) {
+                return null;
+            }
+            Method m = methodRef.get();
+            if (m == null) {
+                Class<?> clazz = classRef.get();
+                if (clazz == null) {
+                    clazz = reLoadClass();
+                    if (clazz != null) {
+                        classRef = new WeakReference<>(clazz);
+                    }
+                }
+                if (clazz == null) {
+                    throw new RuntimeException("Method " + methodName + " for " +
+                            className + " could not be reconstructed - class reference has gone");
+                }
+                Class<?>[] paramTypes = null;
+                if (writeParamClassNames != null) {
+                    paramTypes = new Class[2];
+                    paramTypes[0] = writeParamTypeRef0.get();
+                    if (paramTypes[0] == null) {
+                        paramTypes[0] = reLoadClass(writeParamClassNames[0]);
+                        if (paramTypes[0] != null) {
+                            writeParamTypeRef0 = new WeakReference<>(paramTypes[0]);
+                        }
+                    }
+                    paramTypes[1] = writeParamTypeRef1.get();
+                    if (paramTypes[1] == null) {
+                        paramTypes[1] = reLoadClass(writeParamClassNames[1]);
+                        if (paramTypes[1] != null) {
+                            writeParamTypeRef1 = new WeakReference<>(paramTypes[1]);
+                        }
+                    }
+                } else {
+                    paramTypes = STRING_CLASS_PARAMETER;
+                }
+                try {
+                    m = clazz.getMethod(methodName, paramTypes);
+                    // Un-comment following line for testing
+                    // System.out.println("Recreated Method " + methodName + " for " + className);
+                } catch (final NoSuchMethodException e) {
+                    throw new RuntimeException("Method " + methodName + " for " +
+                            className + " could not be reconstructed - method not found");
+                }
+                methodRef = new SoftReference<>(m);
+            }
+            return m;
+        }
+
+        /**
+         * Try to re-load the class
+         */
+        private Class<?> reLoadClass() {
+            return reLoadClass(className);
+        }
+
+        /**
+         * Try to re-load the class
+         */
+        private Class<?> reLoadClass(final String name) {
+
+            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+            // Try the context class loader
+            if (classLoader != null) {
+                try {
+                    return classLoader.loadClass(name);
+                } catch (final ClassNotFoundException e) {
+                    // ignore
+                }
+            }
+
+            // Try this class's class loader
+            classLoader = MappedPropertyDescriptor.class.getClassLoader();
+            try {
+                return classLoader.loadClass(name);
+            } catch (final ClassNotFoundException e) {
+                return null;
+            }
+        }
+    }
+
+    /**
+     * The parameter types array for the reader method signature.
+     */
+    private static final Class<?>[] STRING_CLASS_PARAMETER = new Class[]{String.class};
+
+    /**
+     * Gets a capitalized version of the specified property name.
+     *
+     * @param s The property name
+     */
+    private static String capitalizePropertyName(final String s) {
+        if (s.isEmpty()) {
+            return s;
+        }
+
+        final char[] chars = s.toCharArray();
+        chars[0] = Character.toUpperCase(chars[0]);
+        return new String(chars);
+    }
+
+    /**
+     * Find a method on a class with a specified parameter list.
+     */
+    private static Method getMethod(final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes)
+                                           throws IntrospectionException {
+        if (methodName == null) {
+            return null;
+        }
+
+        final Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, parameterTypes);
+        if (method != null) {
+            return method;
+        }
+
+        final int parameterCount = parameterTypes == null ? 0 : parameterTypes.length;
+
+        // No Method found
+        throw new IntrospectionException("No method \"" + methodName +
+                "\" with " + parameterCount + " parameter(s) of matching types.");
+    }
+
+    /**
+     * Find a method on a class with a specified number of parameters.
+     */
+    private static Method getMethod(final Class<?> clazz, final String methodName, final int parameterCount)
+            throws IntrospectionException {
+        if (methodName == null) {
+            return null;
+        }
+
+        final Method method = internalGetMethod(clazz, methodName, parameterCount);
+        if (method != null) {
+            return method;
+        }
+
+        // No Method found
+        throw new IntrospectionException("No method \"" + methodName +
+                "\" with " + parameterCount + " parameter(s)");
+    }
+
+    /**
+     * Find a method on a class with a specified number of parameters.
+     */
+    private static Method internalGetMethod(final Class<?> initial, final String methodName,
+                                            final int parameterCount) {
+        // For overridden methods we need to find the most derived version.
+        // So we start with the given class and walk up the superclass chain.
+        for (Class<?> clazz = initial; clazz != null; clazz = clazz.getSuperclass()) {
+            final Method[] methods = clazz.getDeclaredMethods();
+            for (final Method method : methods) {
+                if (method == null) {
+                    continue;
+                }
+                // skip static methods.
+                final int mods = method.getModifiers();
+                if (!Modifier.isPublic(mods) ||
+                    Modifier.isStatic(mods)) {
+                    continue;
+                }
+                if (method.getName().equals(methodName) &&
+                        method.getParameterTypes().length == parameterCount) {
+                    return method;
+                }
+            }
+        }
+
+        // Now check any inherited interfaces.  This is necessary both when
+        // the argument class is itself an interface, and when the argument
+        // class is an abstract class.
+        final Class<?>[] interfaces = initial.getInterfaces();
+        for (final Class<?> interface1 : interfaces) {
+            final Method method = internalGetMethod(interface1, methodName, parameterCount);
+            if (method != null) {
+                return method;
+            }
+        }
+
+        return null;
+    }
+
     /**
      * The underlying data type of the property we are describing.
      */
@@ -58,11 +273,6 @@ public class MappedPropertyDescriptor extends PropertyDescriptor {
      */
     private MappedMethodReference mappedWriteMethodRef;
 
-    /**
-     * The parameter types array for the reader method signature.
-     */
-    private static final Class<?>[] STRING_CLASS_PARAMETER = new Class[]{String.class};
-
     /**
      * Constructs a MappedPropertyDescriptor for a property that follows
      * the standard Java convention by having getFoo and setFoo
@@ -204,66 +414,6 @@ public class MappedPropertyDescriptor extends PropertyDescriptor {
         findMappedPropertyType();
     }
 
-    /**
-     * Gets the Class object for the property values.
-     *
-     * @return The Java type info for the property values.  Note that
-     * the "Class" object may describe a built-in Java type such as "int".
-     * The result may be "null" if this is a mapped property that
-     * does not support non-keyed access.
-     * <p>
-     * This is the type that will be returned by the mappedReadMethod.
-     */
-    public Class<?> getMappedPropertyType() {
-        return mappedPropertyTypeRef.get();
-    }
-
-    /**
-     * Gets the method that should be used to read one of the property value.
-     *
-     * @return The method that should be used to read the property value.
-     * May return null if the property can't be read.
-     */
-    public Method getMappedReadMethod() {
-        return mappedReadMethodRef.get();
-    }
-
-    /**
-     * Sets the method that should be used to read one of the property value.
-     *
-     * @param mappedGetter The mapped getter method.
-     * @throws IntrospectionException If an error occurs finding the
-     * mapped property
-     */
-    public void setMappedReadMethod(final Method mappedGetter)
-            throws IntrospectionException {
-        mappedReadMethodRef = new MappedMethodReference(mappedGetter);
-        findMappedPropertyType();
-    }
-
-    /**
-     * Gets the method that should be used to write one of the property value.
-     *
-     * @return The method that should be used to write one of the property value.
-     * May return null if the property can't be written.
-     */
-    public Method getMappedWriteMethod() {
-        return mappedWriteMethodRef.get();
-    }
-
-    /**
-     * Sets the method that should be used to write the property value.
-     *
-     * @param mappedSetter The mapped setter method.
-     * @throws IntrospectionException If an error occurs finding the
-     * mapped property
-     */
-    public void setMappedWriteMethod(final Method mappedSetter)
-            throws IntrospectionException {
-        mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
-        findMappedPropertyType();
-    }
-
     /**
      * Introspect our bean class to identify the corresponding getter
      * and setter methods.
@@ -302,212 +452,62 @@ public class MappedPropertyDescriptor extends PropertyDescriptor {
     }
 
     /**
-     * Gets a capitalized version of the specified property name.
+     * Gets the Class object for the property values.
      *
-     * @param s The property name
+     * @return The Java type info for the property values.  Note that
+     * the "Class" object may describe a built-in Java type such as "int".
+     * The result may be "null" if this is a mapped property that
+     * does not support non-keyed access.
+     * <p>
+     * This is the type that will be returned by the mappedReadMethod.
      */
-    private static String capitalizePropertyName(final String s) {
-        if (s.isEmpty()) {
-            return s;
-        }
-
-        final char[] chars = s.toCharArray();
-        chars[0] = Character.toUpperCase(chars[0]);
-        return new String(chars);
+    public Class<?> getMappedPropertyType() {
+        return mappedPropertyTypeRef.get();
     }
 
     /**
-     * Find a method on a class with a specified number of parameters.
+     * Gets the method that should be used to read one of the property value.
+     *
+     * @return The method that should be used to read the property value.
+     * May return null if the property can't be read.
      */
-    private static Method internalGetMethod(final Class<?> initial, final String methodName,
-                                            final int parameterCount) {
-        // For overridden methods we need to find the most derived version.
-        // So we start with the given class and walk up the superclass chain.
-        for (Class<?> clazz = initial; clazz != null; clazz = clazz.getSuperclass()) {
-            final Method[] methods = clazz.getDeclaredMethods();
-            for (final Method method : methods) {
-                if (method == null) {
-                    continue;
-                }
-                // skip static methods.
-                final int mods = method.getModifiers();
-                if (!Modifier.isPublic(mods) ||
-                    Modifier.isStatic(mods)) {
-                    continue;
-                }
-                if (method.getName().equals(methodName) &&
-                        method.getParameterTypes().length == parameterCount) {
-                    return method;
-                }
-            }
-        }
-
-        // Now check any inherited interfaces.  This is necessary both when
-        // the argument class is itself an interface, and when the argument
-        // class is an abstract class.
-        final Class<?>[] interfaces = initial.getInterfaces();
-        for (final Class<?> interface1 : interfaces) {
-            final Method method = internalGetMethod(interface1, methodName, parameterCount);
-            if (method != null) {
-                return method;
-            }
-        }
-
-        return null;
+    public Method getMappedReadMethod() {
+        return mappedReadMethodRef.get();
     }
 
     /**
-     * Find a method on a class with a specified number of parameters.
+     * Gets the method that should be used to write one of the property value.
+     *
+     * @return The method that should be used to write one of the property value.
+     * May return null if the property can't be written.
      */
-    private static Method getMethod(final Class<?> clazz, final String methodName, final int parameterCount)
-            throws IntrospectionException {
-        if (methodName == null) {
-            return null;
-        }
-
-        final Method method = internalGetMethod(clazz, methodName, parameterCount);
-        if (method != null) {
-            return method;
-        }
-
-        // No Method found
-        throw new IntrospectionException("No method \"" + methodName +
-                "\" with " + parameterCount + " parameter(s)");
+    public Method getMappedWriteMethod() {
+        return mappedWriteMethodRef.get();
     }
 
     /**
-     * Find a method on a class with a specified parameter list.
+     * Sets the method that should be used to read one of the property value.
+     *
+     * @param mappedGetter The mapped getter method.
+     * @throws IntrospectionException If an error occurs finding the
+     * mapped property
      */
-    private static Method getMethod(final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes)
-                                           throws IntrospectionException {
-        if (methodName == null) {
-            return null;
-        }
-
-        final Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, parameterTypes);
-        if (method != null) {
-            return method;
-        }
-
-        final int parameterCount = parameterTypes == null ? 0 : parameterTypes.length;
-
-        // No Method found
-        throw new IntrospectionException("No method \"" + methodName +
-                "\" with " + parameterCount + " parameter(s) of matching types.");
+    public void setMappedReadMethod(final Method mappedGetter)
+            throws IntrospectionException {
+        mappedReadMethodRef = new MappedMethodReference(mappedGetter);
+        findMappedPropertyType();
     }
 
     /**
-     * Holds a {@link Method} in a {@link SoftReference} so that it
-     * it doesn't prevent any ClassLoader being garbage collected, but
-     * tries to re-create the method if the method reference has been
-     * released.
+     * Sets the method that should be used to write the property value.
      *
-     * See https://issues.apache.org/jira/browse/BEANUTILS-291
+     * @param mappedSetter The mapped setter method.
+     * @throws IntrospectionException If an error occurs finding the
+     * mapped property
      */
-    private static class MappedMethodReference {
-        private String className;
-        private String methodName;
-        private Reference<Method> methodRef;
-        private Reference<Class<?>> classRef;
-        private Reference<Class<?>> writeParamTypeRef0;
-        private Reference<Class<?>> writeParamTypeRef1;
-        private String[] writeParamClassNames;
-        MappedMethodReference(final Method m) {
-            if (m != null) {
-                className = m.getDeclaringClass().getName();
-                methodName = m.getName();
-                methodRef = new SoftReference<>(m);
-                classRef = new WeakReference<>(m.getDeclaringClass());
-                final Class<?>[] types = m.getParameterTypes();
-                if (types.length == 2) {
-                    writeParamTypeRef0 = new WeakReference<>(types[0]);
-                    writeParamTypeRef1 = new WeakReference<>(types[1]);
-                    writeParamClassNames = new String[2];
-                    writeParamClassNames[0] = types[0].getName();
-                    writeParamClassNames[1] = types[1].getName();
-                }
-            }
-        }
-        private Method get() {
-            if (methodRef == null) {
-                return null;
-            }
-            Method m = methodRef.get();
-            if (m == null) {
-                Class<?> clazz = classRef.get();
-                if (clazz == null) {
-                    clazz = reLoadClass();
-                    if (clazz != null) {
-                        classRef = new WeakReference<>(clazz);
-                    }
-                }
-                if (clazz == null) {
-                    throw new RuntimeException("Method " + methodName + " for " +
-                            className + " could not be reconstructed - class reference has gone");
-                }
-                Class<?>[] paramTypes = null;
-                if (writeParamClassNames != null) {
-                    paramTypes = new Class[2];
-                    paramTypes[0] = writeParamTypeRef0.get();
-                    if (paramTypes[0] == null) {
-                        paramTypes[0] = reLoadClass(writeParamClassNames[0]);
-                        if (paramTypes[0] != null) {
-                            writeParamTypeRef0 = new WeakReference<>(paramTypes[0]);
-                        }
-                    }
-                    paramTypes[1] = writeParamTypeRef1.get();
-                    if (paramTypes[1] == null) {
-                        paramTypes[1] = reLoadClass(writeParamClassNames[1]);
-                        if (paramTypes[1] != null) {
-                            writeParamTypeRef1 = new WeakReference<>(paramTypes[1]);
-                        }
-                    }
-                } else {
-                    paramTypes = STRING_CLASS_PARAMETER;
-                }
-                try {
-                    m = clazz.getMethod(methodName, paramTypes);
-                    // Un-comment following line for testing
-                    // System.out.println("Recreated Method " + methodName + " for " + className);
-                } catch (final NoSuchMethodException e) {
-                    throw new RuntimeException("Method " + methodName + " for " +
-                            className + " could not be reconstructed - method not found");
-                }
-                methodRef = new SoftReference<>(m);
-            }
-            return m;
-        }
-
-        /**
-         * Try to re-load the class
-         */
-        private Class<?> reLoadClass() {
-            return reLoadClass(className);
-        }
-
-        /**
-         * Try to re-load the class
-         */
-        private Class<?> reLoadClass(final String name) {
-
-            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
-
-            // Try the context class loader
-            if (classLoader != null) {
-                try {
-                    return classLoader.loadClass(name);
-                } catch (final ClassNotFoundException e) {
-                    // ignore
-                }
-            }
-
-            // Try this class's class loader
-            classLoader = MappedPropertyDescriptor.class.getClassLoader();
-            try {
-                return classLoader.loadClass(name);
-            } catch (final ClassNotFoundException e) {
-                return null;
-            }
-        }
+    public void setMappedWriteMethod(final Method mappedSetter)
+            throws IntrospectionException {
+        mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
+        findMappedPropertyType();
     }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/MethodUtils.java b/src/main/java/org/apache/commons/beanutils2/MethodUtils.java
index 5f0553e2..406b6745 100644
--- a/src/main/java/org/apache/commons/beanutils2/MethodUtils.java
+++ b/src/main/java/org/apache/commons/beanutils2/MethodUtils.java
@@ -46,6 +46,73 @@ import org.apache.commons.logging.LogFactory;
  */
 public class MethodUtils {
 
+    /**
+     * Represents the key to looking up a Method by reflection.
+     */
+    private static class MethodDescriptor {
+        private final Class<?> cls;
+        private final String methodName;
+        private final Class<?>[] paramTypes;
+        private final boolean exact;
+        private final int hashCode;
+
+        /**
+         * The sole constructor.
+         *
+         * @param cls  the class to reflect, must not be null
+         * @param methodName  the method name to obtain
+         * @param paramTypes the array of classes representing the parameter types
+         * @param exact whether the match has to be exact.
+         */
+        public MethodDescriptor(final Class<?> cls, final String methodName, Class<?>[] paramTypes,
+                final boolean exact) {
+            if (cls == null) {
+                throw new IllegalArgumentException("Class cannot be null");
+            }
+            if (methodName == null) {
+                throw new IllegalArgumentException("Method Name cannot be null");
+            }
+            if (paramTypes == null) {
+                paramTypes = BeanUtils.EMPTY_CLASS_ARRAY;
+            }
+
+            this.cls = cls;
+            this.methodName = methodName;
+            this.paramTypes = paramTypes;
+            this.exact= exact;
+
+            this.hashCode = methodName.length();
+        }
+        /**
+         * Checks for equality.
+         * @param obj object to be tested for equality
+         * @return true, if the object describes the same Method.
+         */
+        @Override
+        public boolean equals(final Object obj) {
+            if (!(obj instanceof MethodDescriptor)) {
+                return false;
+            }
+            final MethodDescriptor md = (MethodDescriptor)obj;
+
+            return exact == md.exact &&
+            methodName.equals(md.methodName) &&
+            cls.equals(md.cls) &&
+            java.util.Arrays.equals(paramTypes, md.paramTypes);
+        }
+        /**
+         * Returns the string length of method name. I.e. if the
+         * hashcodes are different, the objects are different. If the
+         * hashcodes are the same, need to use the equals method to
+         * determine equality.
+         * @return the string length of method name.
+         */
+        @Override
+        public int hashCode() {
+            return hashCode;
+        }
+    }
+
     private static final Log LOG = LogFactory.getLog(MethodUtils.class);
 
     /**
@@ -95,17 +162,14 @@ public class MethodUtils {
             .synchronizedMap(new WeakHashMap<MethodDescriptor, Reference<Method>>());
 
     /**
-     * Sets whether methods should be cached for greater performance or not,
-     * default is {@code true}.
+     * Add a method to the cache.
      *
-     * @param cacheMethods {@code true} if methods should be
-     * cached for greater performance, otherwise {@code false}
-     * @since 1.8.0
+     * @param md The method descriptor
+     * @param method The method to cache
      */
-    public static synchronized void setCacheMethods(final boolean cacheMethods) {
-        CACHE_METHODS = cacheMethods;
-        if (!CACHE_METHODS) {
-            clearCache();
+    private static void cacheMethod(final MethodDescriptor md, final Method method) {
+        if (CACHE_METHODS && method != null) {
+            cache.put(md, new WeakReference<>(method));
         }
     }
 
@@ -121,330 +185,499 @@ public class MethodUtils {
     }
 
     /**
-     * <p>Invoke a named method whose parameter type matches the object type.</p>
-     *
-     * <p>The behavior of this method is less deterministic
-     * than {@code invokeExactMethod()}.
-     * It loops through all methods with names that match
-     * and then executes the first it finds with compatible parameters.</p>
-     *
-     * <p>This method supports calls to methods taking primitive parameters
-     * via passing in wrapping classes. So, for example, a {@code Boolean} class
-     * would match a {@code boolean} primitive.</p>
+     * <p>Return an accessible method (that is, one that can be invoked via
+     * reflection) that implements the specified Method.  If no such method
+     * can be found, return {@code null}.</p>
      *
-     * <p> This is a convenient wrapper for
-     * {@link #invokeMethod(Object object,String methodName,Object [] args)}.
-     * </p>
+     * @param clazz The class of the object
+     * @param method The method that we wish to call
+     * @return The accessible method
+     * @since 1.8.0
+     */
+    public static Method getAccessibleMethod(Class<?> clazz, Method method) {
+        // Make sure we have a method to check
+        if (method == null) {
+            return null;
+        }
+
+        // If the requested method is not public we cannot call it
+        if (!Modifier.isPublic(method.getModifiers())) {
+            return null;
+        }
+
+        boolean sameClass = true;
+        if (clazz == null) {
+            clazz = method.getDeclaringClass();
+        } else {
+            if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
+                throw new IllegalArgumentException(clazz.getName() +
+                        " is not assignable from " + method.getDeclaringClass().getName());
+            }
+            sameClass = clazz.equals(method.getDeclaringClass());
+        }
+
+        // If the class is public, we are done
+        if (Modifier.isPublic(clazz.getModifiers())) {
+            if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
+                setMethodAccessible(method); // Default access superclass workaround
+            }
+            return method;
+        }
+
+        final String methodName      = method.getName();
+        final Class<?>[] parameterTypes = method.getParameterTypes();
+
+        // Check the implemented interfaces and subinterfaces
+        method =
+                getAccessibleMethodFromInterfaceNest(clazz,
+                        methodName,
+                        parameterTypes);
+
+        // Check the superclass chain
+        if (method == null) {
+            method = getAccessibleMethodFromSuperclass(clazz,
+                        methodName,
+                        parameterTypes);
+        }
+
+        return method;
+    }
+
+    /**
+     * <p>Return an accessible method (that is, one that can be invoked via
+     * reflection) with given name and a single parameter.  If no such method
+     * can be found, return {@code null}.
+     * Basically, a convenience wrapper that constructs a {@code Class}
+     * array for you.</p>
      *
-     * @param object invoke method on this object
+     * @param clazz get method from this class
      * @param methodName get method with this name
-     * @param arg use this argument. May be null (this will result in calling the
-     *  parameterless method with name {@code methodName}).
-     * @return The value returned by the invoked method
-     *
-     * @throws NoSuchMethodException if there is no such accessible method
-     * @throws InvocationTargetException wraps an exception thrown by the
-     *  method invoked
-     * @throws IllegalAccessException if the requested method is not accessible
-     *  via reflection
+     * @param parameterType taking this type of parameter
+     * @return The accessible method
      */
-    public static Object invokeMethod(
-            final Object object,
+    public static Method getAccessibleMethod(
+            final Class<?> clazz,
             final String methodName,
-            final Object arg)
-            throws
-            NoSuchMethodException,
-            IllegalAccessException,
-            InvocationTargetException {
-        final Object[] args = toArray(arg);
-        return invokeMethod(object, methodName, args);
+            final Class<?> parameterType) {
+        final Class<?>[] parameterTypes = {parameterType};
+        return getAccessibleMethod(clazz, methodName, parameterTypes);
     }
 
     /**
-     * <p>Invoke a named method whose parameter type matches the object type.</p>
-     *
-     * <p>The behavior of this method is less deterministic
-     * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
-     * It loops through all methods with names that match
-     * and then executes the first it finds with compatible parameters.</p>
-     *
-     * <p>This method supports calls to methods taking primitive parameters
-     * via passing in wrapping classes. So, for example, a {@code Boolean} class
-     * would match a {@code boolean} primitive.</p>
-     *
-     * <p> This is a convenient wrapper for
-     * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
-     * </p>
+     * <p>Return an accessible method (that is, one that can be invoked via
+     * reflection) with given name and parameters.  If no such method
+     * can be found, return {@code null}.
+     * This is just a convenient wrapper for
+     * {@link #getAccessibleMethod(Method method)}.</p>
      *
-     * @param object invoke method on this object
+     * @param clazz get method from this class
      * @param methodName get method with this name
-     * @param args use these arguments - treat null as empty array (passing null will
-     *  result in calling the parameterless method with name {@code methodName}).
-     * @return The value returned by the invoked method
-     *
-     * @throws NoSuchMethodException if there is no such accessible method
-     * @throws InvocationTargetException wraps an exception thrown by the
-     *  method invoked
-     * @throws IllegalAccessException if the requested method is not accessible
-     *  via reflection
+     * @param parameterTypes with these parameters types
+     * @return The accessible method
      */
-    public static Object invokeMethod(
-            final Object object,
+    public static Method getAccessibleMethod(
+            final Class<?> clazz,
             final String methodName,
-            Object[] args)
-            throws
-            NoSuchMethodException,
-            IllegalAccessException,
-            InvocationTargetException {
-        if (args == null) {
-            args = BeanUtils.EMPTY_OBJECT_ARRAY;
-        }
-        final int arguments = args.length;
-        final Class<?>[] parameterTypes = new Class[arguments];
-        for (int i = 0; i < arguments; i++) {
-            parameterTypes[i] = args[i].getClass();
+            final Class<?>[] parameterTypes) {
+        try {
+            final MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true);
+            // Check the cache first
+            Method method = getCachedMethod(md);
+            if (method != null) {
+                return method;
+            }
+
+            method =  getAccessibleMethod
+                    (clazz, clazz.getMethod(methodName, parameterTypes));
+            cacheMethod(md, method);
+            return method;
+        } catch (final NoSuchMethodException e) {
+            return null;
         }
-        return invokeMethod(object, methodName, args, parameterTypes);
     }
 
     /**
-     * <p>Invoke a named method whose parameter type matches the object type.</p>
+     * <p>Return an accessible method (that is, one that can be invoked via
+     * reflection) that implements the specified Method.  If no such method
+     * can be found, return {@code null}.</p>
      *
-     * <p>The behavior of this method is less deterministic
-     * than {@link
-     * #invokeExactMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
-     * It loops through all methods with names that match
-     * and then executes the first it finds with compatible parameters.</p>
-     *
-     * <p>This method supports calls to methods taking primitive parameters
-     * via passing in wrapping classes. So, for example, a {@code Boolean} class
-     * would match a {@code boolean} primitive.</p>
-     *
-     *
-     * @param object invoke method on this object
-     * @param methodName get method with this name
-     * @param args use these arguments - treat null as empty array (passing null will
-     *  result in calling the parameterless method with name {@code methodName}).
-     * @param parameterTypes match these parameters - treat null as empty array
-     * @return The value returned by the invoked method
-     *
-     * @throws NoSuchMethodException if there is no such accessible method
-     * @throws InvocationTargetException wraps an exception thrown by the
-     *  method invoked
-     * @throws IllegalAccessException if the requested method is not accessible
-     *  via reflection
+     * @param method The method that we wish to call
+     * @return The accessible method
      */
-    public static Object invokeMethod(
-            final Object object,
-            final String methodName,
-            Object[] args,
-            Class<?>[] parameterTypes)
-                throws
-                    NoSuchMethodException,
-                    IllegalAccessException,
-                    InvocationTargetException {
-        if (parameterTypes == null) {
-            parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
-        }
-        if (args == null) {
-            args = BeanUtils.EMPTY_OBJECT_ARRAY;
-        }
-
-        final Method method = getMatchingAccessibleMethod(
-                object.getClass(),
-                methodName,
-                parameterTypes);
+    public static Method getAccessibleMethod(final Method method) {
+        // Make sure we have a method to check
         if (method == null) {
-            throw new NoSuchMethodException("No such accessible method: " +
-                    methodName + "() on object: " + object.getClass().getName());
+            return null;
         }
-        return method.invoke(object, args);
+
+        return getAccessibleMethod(method.getDeclaringClass(), method);
     }
 
     /**
-     * <p>Invoke a method whose parameter type matches exactly the object
-     * type.</p>
-     *
-     * <p> This is a convenient wrapper for
-     * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
-     * </p>
+     * <p>Return an accessible method (that is, one that can be invoked via
+     * reflection) that implements the specified method, by scanning through
+     * all implemented interfaces and subinterfaces.  If no such method
+     * can be found, return {@code null}.</p>
      *
-     * @param object invoke method on this object
-     * @param methodName get method with this name
-     * @param arg use this argument. May be null (this will result in calling the
-     *  parameterless method with name {@code methodName}).
-     * @return The value returned by the invoked method
+     * <p> There isn't any good reason why this method must be private.
+     * It is because there doesn't seem any reason why other classes should
+     * call this rather than the higher level methods.</p>
      *
-     * @throws NoSuchMethodException if there is no such accessible method
-     * @throws InvocationTargetException wraps an exception thrown by the
-     *  method invoked
-     * @throws IllegalAccessException if the requested method is not accessible
-     *  via reflection
+     * @param clazz Parent class for the interfaces to be checked
+     * @param methodName Method name of the method we wish to call
+     * @param parameterTypes The parameter type signatures
      */
-    public static Object invokeExactMethod(
-            final Object object,
-            final String methodName,
-            final Object arg)
-            throws
-            NoSuchMethodException,
-            IllegalAccessException,
-            InvocationTargetException {
-        final Object[] args = toArray(arg);
-        return invokeExactMethod(object, methodName, args);
+    private static Method getAccessibleMethodFromInterfaceNest
+            (Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) {
+        Method method = null;
+
+        // Search up the superclass chain
+        for (; clazz != null; clazz = clazz.getSuperclass()) {
+
+            // Check the implemented interfaces of the parent class
+            final Class<?>[] interfaces = clazz.getInterfaces();
+            for (final Class<?> anInterface : interfaces) {
+
+                // Is this interface public?
+                if (!Modifier.isPublic(anInterface.getModifiers())) {
+                    continue;
+                }
+
+                // Does the method exist on this interface?
+                try {
+                    method = anInterface.getDeclaredMethod(methodName,
+                            parameterTypes);
+                } catch (final NoSuchMethodException e) {
+                    /* Swallow, if no method is found after the loop then this
+                     * method returns null.
+                     */
+                }
+                if (method != null) {
+                    return method;
+                }
+
+                // Recursively check our parent interfaces
+                method =
+                        getAccessibleMethodFromInterfaceNest(anInterface,
+                                methodName,
+                                parameterTypes);
+                if (method != null) {
+                    return method;
+                }
+
+            }
+
+        }
+
+        // We did not find anything
+        return null;
     }
 
     /**
-     * <p>Invoke a method whose parameter types match exactly the object
-     * types.</p>
-     *
-     * <p> This uses reflection to invoke the method obtained from a call to
-     * {@code getAccessibleMethod()}.</p>
-     *
-     * @param object invoke method on this object
-     * @param methodName get method with this name
-     * @param args use these arguments - treat null as empty array (passing null will
-     *  result in calling the parameterless method with name {@code methodName}).
-     * @return The value returned by the invoked method
+     * <p>Return an accessible method (that is, one that can be invoked via
+     * reflection) by scanning through the superclasses. If no such method
+     * can be found, return {@code null}.</p>
      *
-     * @throws NoSuchMethodException if there is no such accessible method
-     * @throws InvocationTargetException wraps an exception thrown by the
-     *  method invoked
-     * @throws IllegalAccessException if the requested method is not accessible
-     *  via reflection
+     * @param clazz Class to be checked
+     * @param methodName Method name of the method we wish to call
+     * @param parameterTypes The parameter type signatures
      */
-    public static Object invokeExactMethod(
-            final Object object,
-            final String methodName,
-            Object[] args)
-            throws
-            NoSuchMethodException,
-            IllegalAccessException,
-            InvocationTargetException {
-        if (args == null) {
-            args = BeanUtils.EMPTY_OBJECT_ARRAY;
-        }
-        final int arguments = args.length;
-        final Class<?>[] parameterTypes = new Class[arguments];
-        for (int i = 0; i < arguments; i++) {
-            parameterTypes[i] = args[i].getClass();
+    private static Method getAccessibleMethodFromSuperclass
+            (final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) {
+        Class<?> parentClazz = clazz.getSuperclass();
+        while (parentClazz != null) {
+            if (Modifier.isPublic(parentClazz.getModifiers())) {
+                try {
+                    return parentClazz.getMethod(methodName, parameterTypes);
+                } catch (final NoSuchMethodException e) {
+                    return null;
+                }
+            }
+            parentClazz = parentClazz.getSuperclass();
         }
-        return invokeExactMethod(object, methodName, args, parameterTypes);
+        return null;
     }
 
     /**
-     * <p>Invoke a method whose parameter types match exactly the parameter
-     * types given.</p>
-     *
-     * <p>This uses reflection to invoke the method obtained from a call to
-     * {@code getAccessibleMethod()}.</p>
-     *
-     * @param object invoke method on this object
-     * @param methodName get method with this name
-     * @param args use these arguments - treat null as empty array (passing null will
-     *  result in calling the parameterless method with name {@code methodName}).
-     * @param parameterTypes match these parameters - treat null as empty array
-     * @return The value returned by the invoked method
+     * Gets the method from the cache, if present.
      *
-     * @throws NoSuchMethodException if there is no such accessible method
-     * @throws InvocationTargetException wraps an exception thrown by the
-     *  method invoked
-     * @throws IllegalAccessException if the requested method is not accessible
-     *  via reflection
+     * @param md The method descriptor
+     * @return The cached method
      */
-    public static Object invokeExactMethod(
-            final Object object,
-            final String methodName,
-            Object[] args,
-            Class<?>[] parameterTypes)
-            throws
-            NoSuchMethodException,
-            IllegalAccessException,
-            InvocationTargetException {
-        if (args == null) {
-            args = BeanUtils.EMPTY_OBJECT_ARRAY;
-        }
-
-        if (parameterTypes == null) {
-            parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
-        }
-
-        final Method method = getAccessibleMethod(
-                object.getClass(),
-                methodName,
-                parameterTypes);
-        if (method == null) {
-            throw new NoSuchMethodException("No such accessible method: " +
-                    methodName + "() on object: " + object.getClass().getName());
+    private static Method getCachedMethod(final MethodDescriptor md) {
+        if (CACHE_METHODS) {
+            final Reference<Method> methodRef = cache.get(md);
+            if (methodRef != null) {
+                return methodRef.get();
+            }
         }
-        return method.invoke(object, args);
+        return null;
     }
 
     /**
-     * <p>Invoke a static method whose parameter types match exactly the parameter
-     * types given.</p>
+     * <p>Find an accessible method that matches the given name and has compatible parameters.
+     * Compatible parameters mean that every method parameter is assignable from
+     * the given parameters.
+     * In other words, it finds a method with the given name
+     * that will take the parameters given.</p>
      *
-     * <p>This uses reflection to invoke the method obtained from a call to
-     * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
+     * <p>This method is slightly indeterministic since it loops
+     * through methods names and return the first matching method.</p>
      *
-     * @param objectClass invoke static method on this class
-     * @param methodName get method with this name
-     * @param args use these arguments - treat null as empty array (passing null will
-     *  result in calling the parameterless method with name {@code methodName}).
-     * @param parameterTypes match these parameters - treat null as empty array
-     * @return The value returned by the invoked method
+     * <p>This method is used by
+     * {@link
+     * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
      *
-     * @throws NoSuchMethodException if there is no such accessible method
-     * @throws InvocationTargetException wraps an exception thrown by the
-     *  method invoked
-     * @throws IllegalAccessException if the requested method is not accessible
-     *  via reflection
-     * @since 1.8.0
+     * <p>This method can match primitive parameter by passing in wrapper classes.
+     * For example, a {@code Boolean</code> will match a primitive <code>boolean}
+     * parameter.
+     *
+     * @param clazz find method in this class
+     * @param methodName find method with this name
+     * @param parameterTypes find method with compatible parameters
+     * @return The accessible method
      */
-    public static Object invokeExactStaticMethod(
-            final Class<?> objectClass,
-            final String methodName,
-            Object[] args,
-            Class<?>[] parameterTypes)
-            throws
-            NoSuchMethodException,
-            IllegalAccessException,
-            InvocationTargetException {
-        if (args == null) {
-            args = BeanUtils.EMPTY_OBJECT_ARRAY;
+    public static Method getMatchingAccessibleMethod(
+                                                final Class<?> clazz,
+                                                final String methodName,
+                                                final Class<?>[] parameterTypes) {
+        // trace logging
+        if (LOG.isTraceEnabled()) {
+            LOG.trace("Matching name=" + methodName + " on " + clazz);
         }
+        final MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
 
-        if (parameterTypes == null) {
-            parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
+        // see if we can find the method directly
+        // most of the time this works and it's much faster
+        try {
+            // Check the cache first
+            Method method = getCachedMethod(md);
+            if (method != null) {
+                return method;
+            }
+
+            method = clazz.getMethod(methodName, parameterTypes);
+            if (LOG.isTraceEnabled()) {
+                LOG.trace("Found straight match: " + method);
+                LOG.trace("isPublic:" + Modifier.isPublic(method.getModifiers()));
+            }
+
+            setMethodAccessible(method); // Default access superclass workaround
+
+            cacheMethod(md, method);
+            return method;
+
+        } catch (final NoSuchMethodException e) { /* SWALLOW */ }
+
+        // search through all methods
+        final int paramSize = parameterTypes.length;
+        Method bestMatch = null;
+        final Method[] methods = clazz.getMethods();
+        float bestMatchCost = Float.MAX_VALUE;
+        float myCost = Float.MAX_VALUE;
+        for (final Method method2 : methods) {
+            if (method2.getName().equals(methodName)) {
+                // log some trace information
+                if (LOG.isTraceEnabled()) {
+                    LOG.trace("Found matching name:");
+                    LOG.trace(method2);
+                }
+
+                // compare parameters
+                final Class<?>[] methodsParams = method2.getParameterTypes();
+                final int methodParamSize = methodsParams.length;
+                if (methodParamSize == paramSize) {
+                    boolean match = true;
+                    for (int n = 0 ; n < methodParamSize; n++) {
+                        if (LOG.isTraceEnabled()) {
+                            LOG.trace("Param=" + parameterTypes[n].getName());
+                            LOG.trace("Method=" + methodsParams[n].getName());
+                        }
+                        if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
+                            if (LOG.isTraceEnabled()) {
+                                LOG.trace(methodsParams[n] + " is not assignable from "
+                                            + parameterTypes[n]);
+                            }
+                            match = false;
+                            break;
+                        }
+                    }
+
+                    if (match) {
+                        // get accessible version of method
+                        final Method method = getAccessibleMethod(clazz, method2);
+                        if (method != null) {
+                            if (LOG.isTraceEnabled()) {
+                                LOG.trace(method + " accessible version of "
+                                            + method2);
+                            }
+                            setMethodAccessible(method); // Default access superclass workaround
+                            myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes());
+                            if ( myCost < bestMatchCost ) {
+                               bestMatch = method;
+                               bestMatchCost = myCost;
+                            }
+                        }
+
+                        LOG.trace("Couldn't find accessible method.");
+                    }
+                }
+            }
+        }
+        if (bestMatch != null) {
+            cacheMethod(md, bestMatch);
+        } else {
+            // didn't find a match
+            LOG.trace("No match found.");
         }
 
-        final Method method = getAccessibleMethod(
-                objectClass,
-                methodName,
-                parameterTypes);
-        if (method == null) {
-            throw new NoSuchMethodException("No such accessible method: " +
-                    methodName + "() on class: " + objectClass.getName());
+        return bestMatch;
+    }
+
+    /**
+     * Gets the number of steps required needed to turn the source class into the
+     * destination class. This represents the number of steps in the object hierarchy
+     * graph.
+     * @param srcClass The source class
+     * @param destClass The destination class
+     * @return The cost of transforming an object
+     */
+    private static float getObjectTransformationCost(Class<?> srcClass, final Class<?> destClass) {
+        float cost = 0.0f;
+        while (srcClass != null && !destClass.equals(srcClass)) {
+            if (destClass.isPrimitive()) {
+                final Class<?> destClassWrapperClazz = getPrimitiveWrapper(destClass);
+                if (destClassWrapperClazz != null && destClassWrapperClazz.equals(srcClass)) {
+                    cost += 0.25f;
+                    break;
+                }
+            }
+            if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) {
+                // slight penalty for interface match.
+                // we still want an exact match to override an interface match, but
+                // an interface match should override anything where we have to get a
+                // superclass.
+                cost += 0.25f;
+                break;
+            }
+            cost++;
+            srcClass = srcClass.getSuperclass();
         }
-        return method.invoke(null, args);
+
+        /*
+         * If the destination class is null, we've traveled all the way up to
+         * an Object match. We'll penalize this by adding 1.5 to the cost.
+         */
+        if (srcClass == null) {
+            cost += 1.5f;
+        }
+
+        return cost;
     }
 
     /**
-     * <p>Invoke a named static method whose parameter type matches the object type.</p>
-     *
-     * <p>The behavior of this method is less deterministic
-     * than {@link #invokeExactMethod(Object, String, Object[], Class[])}.
-     * It loops through all methods with names that match
-     * and then executes the first it finds with compatible parameters.</p>
-     *
-     * <p>This method supports calls to methods taking primitive parameters
-     * via passing in wrapping classes. So, for example, a {@code Boolean} class
-     * would match a {@code boolean} primitive.</p>
+     * Gets the class for the primitive type corresponding to the primitive wrapper class given.
+     * For example, an instance of {@code Boolean.class</code> returns a <code>boolean.class}.
+     * @param wrapperType the
+     * @return the primitive type class corresponding to the given wrapper class,
+     * null if no match is found
+     */
+    public static Class<?> getPrimitiveType(final Class<?> wrapperType) {
+        // does anyone know a better strategy than comparing names?
+        if (Boolean.class.equals(wrapperType)) {
+            return boolean.class;
+        }
+        if (Float.class.equals(wrapperType)) {
+            return float.class;
+        }
+        if (Long.class.equals(wrapperType)) {
+            return long.class;
+        }
+        if (Integer.class.equals(wrapperType)) {
+            return int.class;
+        }
+        if (Short.class.equals(wrapperType)) {
+            return short.class;
+        }
+        if (Byte.class.equals(wrapperType)) {
+            return byte.class;
+        }
+        if (Double.class.equals(wrapperType)) {
+            return double.class;
+        }
+        if (Character.class.equals(wrapperType)) {
+            return char.class;
+        }
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Not a known primitive wrapper class: " + wrapperType);
+        }
+        return null;
+    }
+
+    /**
+     * Gets the wrapper object class for the given primitive type class.
+     * For example, passing {@code boolean.class</code> returns <code>Boolean.class}
+     * @param primitiveType the primitive type class for which a match is to be found
+     * @return the wrapper type associated with the given primitive
+     * or null if no match is found
+     */
+    public static Class<?> getPrimitiveWrapper(final Class<?> primitiveType) {
+        // does anyone know a better strategy than comparing names?
+        if (boolean.class.equals(primitiveType)) {
+            return Boolean.class;
+        }
+        if (float.class.equals(primitiveType)) {
+            return Float.class;
+        }
+        if (long.class.equals(primitiveType)) {
+            return Long.class;
+        }
+        if (int.class.equals(primitiveType)) {
+            return Integer.class;
+        }
+        if (short.class.equals(primitiveType)) {
+            return Short.class;
+        }
+        if (byte.class.equals(primitiveType)) {
+            return Byte.class;
+        }
+        if (double.class.equals(primitiveType)) {
+            return Double.class;
+        }
+        if (char.class.equals(primitiveType)) {
+            return Character.class;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the sum of the object transformation cost for each class in the source
+     * argument list.
+     * @param srcArgs The source arguments
+     * @param destArgs The destination arguments
+     * @return The total transformation cost
+     */
+    private static float getTotalTransformationCost(final Class<?>[] srcArgs, final Class<?>[] destArgs) {
+        float totalCost = 0.0f;
+        for (int i = 0; i < srcArgs.length; i++) {
+            Class<?> srcClass, destClass;
+            srcClass = srcArgs[i];
+            destClass = destArgs[i];
+            totalCost += getObjectTransformationCost(srcClass, destClass);
+        }
+
+        return totalCost;
+    }
+
+    /**
+     * <p>Invoke a method whose parameter type matches exactly the object
+     * type.</p>
      *
      * <p> This is a convenient wrapper for
-     * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}.
+     * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
      * </p>
      *
-     * @param objectClass invoke static method on this class
+     * @param object invoke method on this object
      * @param methodName get method with this name
      * @param arg use this argument. May be null (this will result in calling the
      *  parameterless method with name {@code methodName}).
@@ -455,10 +688,9 @@ public class MethodUtils {
      *  method invoked
      * @throws IllegalAccessException if the requested method is not accessible
      *  via reflection
-     * @since 1.8.0
      */
-    public static Object invokeStaticMethod(
-            final Class<?> objectClass,
+    public static Object invokeExactMethod(
+            final Object object,
             final String methodName,
             final Object arg)
             throws
@@ -466,26 +698,17 @@ public class MethodUtils {
             IllegalAccessException,
             InvocationTargetException {
         final Object[] args = toArray(arg);
-        return invokeStaticMethod (objectClass, methodName, args);
+        return invokeExactMethod(object, methodName, args);
     }
 
     /**
-     * <p>Invoke a named static method whose parameter type matches the object type.</p>
-     *
-     * <p>The behavior of this method is less deterministic
-     * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
-     * It loops through all methods with names that match
-     * and then executes the first it finds with compatible parameters.</p>
-     *
-     * <p>This method supports calls to methods taking primitive parameters
-     * via passing in wrapping classes. So, for example, a {@code Boolean} class
-     * would match a {@code boolean} primitive.</p>
+     * <p>Invoke a method whose parameter types match exactly the object
+     * types.</p>
      *
-     * <p> This is a convenient wrapper for
-     * {@link #invokeStaticMethod(Class objectClass, String methodName, Object[] args, Class[] parameterTypes)}.
-     * </p>
+     * <p> This uses reflection to invoke the method obtained from a call to
+     * {@code getAccessibleMethod()}.</p>
      *
-     * @param objectClass invoke static method on this class
+     * @param object invoke method on this object
      * @param methodName get method with this name
      * @param args use these arguments - treat null as empty array (passing null will
      *  result in calling the parameterless method with name {@code methodName}).
@@ -496,10 +719,9 @@ public class MethodUtils {
      *  method invoked
      * @throws IllegalAccessException if the requested method is not accessible
      *  via reflection
-     * @since 1.8.0
      */
-    public static Object invokeStaticMethod(
-            final Class<?> objectClass,
+    public static Object invokeExactMethod(
+            final Object object,
             final String methodName,
             Object[] args)
             throws
@@ -514,24 +736,17 @@ public class MethodUtils {
         for (int i = 0; i < arguments; i++) {
             parameterTypes[i] = args[i].getClass();
         }
-        return invokeStaticMethod (objectClass, methodName, args, parameterTypes);
+        return invokeExactMethod(object, methodName, args, parameterTypes);
     }
 
     /**
-     * <p>Invoke a named static method whose parameter type matches the object type.</p>
-     *
-     * <p>The behavior of this method is less deterministic
-     * than {@link
-     * #invokeExactStaticMethod(Class objectClass, String methodName, Object[] args, Class[] parameterTypes)}.
-     * It loops through all methods with names that match
-     * and then executes the first it finds with compatible parameters.</p>
-     *
-     * <p>This method supports calls to methods taking primitive parameters
-     * via passing in wrapping classes. So, for example, a {@code Boolean} class
-     * would match a {@code boolean} primitive.</p>
+     * <p>Invoke a method whose parameter types match exactly the parameter
+     * types given.</p>
      *
+     * <p>This uses reflection to invoke the method obtained from a call to
+     * {@code getAccessibleMethod()}.</p>
      *
-     * @param objectClass invoke static method on this class
+     * @param object invoke method on this object
      * @param methodName get method with this name
      * @param args use these arguments - treat null as empty array (passing null will
      *  result in calling the parameterless method with name {@code methodName}).
@@ -543,34 +758,33 @@ public class MethodUtils {
      *  method invoked
      * @throws IllegalAccessException if the requested method is not accessible
      *  via reflection
-     * @since 1.8.0
      */
-    public static Object invokeStaticMethod(
-            final Class<?> objectClass,
+    public static Object invokeExactMethod(
+            final Object object,
             final String methodName,
             Object[] args,
             Class<?>[] parameterTypes)
-                throws
-                    NoSuchMethodException,
-                    IllegalAccessException,
-                    InvocationTargetException {
+            throws
+            NoSuchMethodException,
+            IllegalAccessException,
+            InvocationTargetException {
+        if (args == null) {
+            args = BeanUtils.EMPTY_OBJECT_ARRAY;
+        }
 
         if (parameterTypes == null) {
             parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
         }
-        if (args == null) {
-            args = BeanUtils.EMPTY_OBJECT_ARRAY;
-        }
 
-        final Method method = getMatchingAccessibleMethod(
-                objectClass,
+        final Method method = getAccessibleMethod(
+                object.getClass(),
                 methodName,
                 parameterTypes);
         if (method == null) {
             throw new NoSuchMethodException("No such accessible method: " +
-                    methodName + "() on class: " + objectClass.getName());
+                    methodName + "() on object: " + object.getClass().getName());
         }
-        return method.invoke(null, args);
+        return method.invoke(object, args);
     }
 
     /**
@@ -606,6 +820,8 @@ public class MethodUtils {
         return invokeExactStaticMethod (objectClass, methodName, args);
     }
 
+
+
     /**
      * <p>Invoke a static method whose parameter types match exactly the object
      * types.</p>
@@ -645,463 +861,339 @@ public class MethodUtils {
         return invokeExactStaticMethod(objectClass, methodName, args, parameterTypes);
     }
 
-    private static Object[] toArray(final Object arg) {
-        Object[] args = null;
-        if (arg != null) {
-            args = new Object[] { arg };
-        }
-        return args;
-    }
-
     /**
-     * <p>Return an accessible method (that is, one that can be invoked via
-     * reflection) with given name and a single parameter.  If no such method
-     * can be found, return {@code null}.
-     * Basically, a convenience wrapper that constructs a {@code Class}
-     * array for you.</p>
+     * <p>Invoke a static method whose parameter types match exactly the parameter
+     * types given.</p>
      *
-     * @param clazz get method from this class
-     * @param methodName get method with this name
-     * @param parameterType taking this type of parameter
-     * @return The accessible method
-     */
-    public static Method getAccessibleMethod(
-            final Class<?> clazz,
-            final String methodName,
-            final Class<?> parameterType) {
-        final Class<?>[] parameterTypes = {parameterType};
-        return getAccessibleMethod(clazz, methodName, parameterTypes);
-    }
-
-    /**
-     * <p>Return an accessible method (that is, one that can be invoked via
-     * reflection) with given name and parameters.  If no such method
-     * can be found, return {@code null}.
-     * This is just a convenient wrapper for
-     * {@link #getAccessibleMethod(Method method)}.</p>
+     * <p>This uses reflection to invoke the method obtained from a call to
+     * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
      *
-     * @param clazz get method from this class
+     * @param objectClass invoke static method on this class
      * @param methodName get method with this name
-     * @param parameterTypes with these parameters types
-     * @return The accessible method
-     */
-    public static Method getAccessibleMethod(
-            final Class<?> clazz,
-            final String methodName,
-            final Class<?>[] parameterTypes) {
-        try {
-            final MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true);
-            // Check the cache first
-            Method method = getCachedMethod(md);
-            if (method != null) {
-                return method;
-            }
-
-            method =  getAccessibleMethod
-                    (clazz, clazz.getMethod(methodName, parameterTypes));
-            cacheMethod(md, method);
-            return method;
-        } catch (final NoSuchMethodException e) {
-            return null;
-        }
-    }
-
-    /**
-     * <p>Return an accessible method (that is, one that can be invoked via
-     * reflection) that implements the specified Method.  If no such method
-     * can be found, return {@code null}.</p>
-     *
-     * @param method The method that we wish to call
-     * @return The accessible method
-     */
-    public static Method getAccessibleMethod(final Method method) {
-        // Make sure we have a method to check
-        if (method == null) {
-            return null;
-        }
-
-        return getAccessibleMethod(method.getDeclaringClass(), method);
-    }
-
-    /**
-     * <p>Return an accessible method (that is, one that can be invoked via
-     * reflection) that implements the specified Method.  If no such method
-     * can be found, return {@code null}.</p>
+     * @param args use these arguments - treat null as empty array (passing null will
+     *  result in calling the parameterless method with name {@code methodName}).
+     * @param parameterTypes match these parameters - treat null as empty array
+     * @return The value returned by the invoked method
      *
-     * @param clazz The class of the object
-     * @param method The method that we wish to call
-     * @return The accessible method
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
      * @since 1.8.0
-     */
-    public static Method getAccessibleMethod(Class<?> clazz, Method method) {
-        // Make sure we have a method to check
-        if (method == null) {
-            return null;
-        }
-
-        // If the requested method is not public we cannot call it
-        if (!Modifier.isPublic(method.getModifiers())) {
-            return null;
-        }
-
-        boolean sameClass = true;
-        if (clazz == null) {
-            clazz = method.getDeclaringClass();
-        } else {
-            if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
-                throw new IllegalArgumentException(clazz.getName() +
-                        " is not assignable from " + method.getDeclaringClass().getName());
-            }
-            sameClass = clazz.equals(method.getDeclaringClass());
-        }
-
-        // If the class is public, we are done
-        if (Modifier.isPublic(clazz.getModifiers())) {
-            if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
-                setMethodAccessible(method); // Default access superclass workaround
-            }
-            return method;
-        }
-
-        final String methodName      = method.getName();
-        final Class<?>[] parameterTypes = method.getParameterTypes();
-
-        // Check the implemented interfaces and subinterfaces
-        method =
-                getAccessibleMethodFromInterfaceNest(clazz,
-                        methodName,
-                        parameterTypes);
-
-        // Check the superclass chain
-        if (method == null) {
-            method = getAccessibleMethodFromSuperclass(clazz,
-                        methodName,
-                        parameterTypes);
-        }
-
-        return method;
-    }
-
-
-
-    /**
-     * <p>Return an accessible method (that is, one that can be invoked via
-     * reflection) by scanning through the superclasses. If no such method
-     * can be found, return {@code null}.</p>
-     *
-     * @param clazz Class to be checked
-     * @param methodName Method name of the method we wish to call
-     * @param parameterTypes The parameter type signatures
-     */
-    private static Method getAccessibleMethodFromSuperclass
-            (final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) {
-        Class<?> parentClazz = clazz.getSuperclass();
-        while (parentClazz != null) {
-            if (Modifier.isPublic(parentClazz.getModifiers())) {
-                try {
-                    return parentClazz.getMethod(methodName, parameterTypes);
-                } catch (final NoSuchMethodException e) {
-                    return null;
-                }
-            }
-            parentClazz = parentClazz.getSuperclass();
+     */
+    public static Object invokeExactStaticMethod(
+            final Class<?> objectClass,
+            final String methodName,
+            Object[] args,
+            Class<?>[] parameterTypes)
+            throws
+            NoSuchMethodException,
+            IllegalAccessException,
+            InvocationTargetException {
+        if (args == null) {
+            args = BeanUtils.EMPTY_OBJECT_ARRAY;
         }
-        return null;
+
+        if (parameterTypes == null) {
+            parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
+        }
+
+        final Method method = getAccessibleMethod(
+                objectClass,
+                methodName,
+                parameterTypes);
+        if (method == null) {
+            throw new NoSuchMethodException("No such accessible method: " +
+                    methodName + "() on class: " + objectClass.getName());
+        }
+        return method.invoke(null, args);
     }
 
     /**
-     * <p>Return an accessible method (that is, one that can be invoked via
-     * reflection) that implements the specified method, by scanning through
-     * all implemented interfaces and subinterfaces.  If no such method
-     * can be found, return {@code null}.</p>
+     * <p>Invoke a named method whose parameter type matches the object type.</p>
      *
-     * <p> There isn't any good reason why this method must be private.
-     * It is because there doesn't seem any reason why other classes should
-     * call this rather than the higher level methods.</p>
+     * <p>The behavior of this method is less deterministic
+     * than {@code invokeExactMethod()}.
+     * It loops through all methods with names that match
+     * and then executes the first it finds with compatible parameters.</p>
      *
-     * @param clazz Parent class for the interfaces to be checked
-     * @param methodName Method name of the method we wish to call
-     * @param parameterTypes The parameter type signatures
+     * <p>This method supports calls to methods taking primitive parameters
+     * via passing in wrapping classes. So, for example, a {@code Boolean} class
+     * would match a {@code boolean} primitive.</p>
+     *
+     * <p> This is a convenient wrapper for
+     * {@link #invokeMethod(Object object,String methodName,Object [] args)}.
+     * </p>
+     *
+     * @param object invoke method on this object
+     * @param methodName get method with this name
+     * @param arg use this argument. May be null (this will result in calling the
+     *  parameterless method with name {@code methodName}).
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
      */
-    private static Method getAccessibleMethodFromInterfaceNest
-            (Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) {
-        Method method = null;
-
-        // Search up the superclass chain
-        for (; clazz != null; clazz = clazz.getSuperclass()) {
-
-            // Check the implemented interfaces of the parent class
-            final Class<?>[] interfaces = clazz.getInterfaces();
-            for (final Class<?> anInterface : interfaces) {
-
-                // Is this interface public?
-                if (!Modifier.isPublic(anInterface.getModifiers())) {
-                    continue;
-                }
-
-                // Does the method exist on this interface?
-                try {
-                    method = anInterface.getDeclaredMethod(methodName,
-                            parameterTypes);
-                } catch (final NoSuchMethodException e) {
-                    /* Swallow, if no method is found after the loop then this
-                     * method returns null.
-                     */
-                }
-                if (method != null) {
-                    return method;
-                }
-
-                // Recursively check our parent interfaces
-                method =
-                        getAccessibleMethodFromInterfaceNest(anInterface,
-                                methodName,
-                                parameterTypes);
-                if (method != null) {
-                    return method;
-                }
-
-            }
-
-        }
-
-        // We did not find anything
-        return null;
+    public static Object invokeMethod(
+            final Object object,
+            final String methodName,
+            final Object arg)
+            throws
+            NoSuchMethodException,
+            IllegalAccessException,
+            InvocationTargetException {
+        final Object[] args = toArray(arg);
+        return invokeMethod(object, methodName, args);
     }
 
     /**
-     * <p>Find an accessible method that matches the given name and has compatible parameters.
-     * Compatible parameters mean that every method parameter is assignable from
-     * the given parameters.
-     * In other words, it finds a method with the given name
-     * that will take the parameters given.</p>
+     * <p>Invoke a named method whose parameter type matches the object type.</p>
      *
-     * <p>This method is slightly indeterministic since it loops
-     * through methods names and return the first matching method.</p>
+     * <p>The behavior of this method is less deterministic
+     * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+     * It loops through all methods with names that match
+     * and then executes the first it finds with compatible parameters.</p>
      *
-     * <p>This method is used by
-     * {@link
-     * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
+     * <p>This method supports calls to methods taking primitive parameters
+     * via passing in wrapping classes. So, for example, a {@code Boolean} class
+     * would match a {@code boolean} primitive.</p>
      *
-     * <p>This method can match primitive parameter by passing in wrapper classes.
-     * For example, a {@code Boolean</code> will match a primitive <code>boolean}
-     * parameter.
+     * <p> This is a convenient wrapper for
+     * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
+     * </p>
      *
-     * @param clazz find method in this class
-     * @param methodName find method with this name
-     * @param parameterTypes find method with compatible parameters
-     * @return The accessible method
+     * @param object invoke method on this object
+     * @param methodName get method with this name
+     * @param args use these arguments - treat null as empty array (passing null will
+     *  result in calling the parameterless method with name {@code methodName}).
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
      */
-    public static Method getMatchingAccessibleMethod(
-                                                final Class<?> clazz,
-                                                final String methodName,
-                                                final Class<?>[] parameterTypes) {
-        // trace logging
-        if (LOG.isTraceEnabled()) {
-            LOG.trace("Matching name=" + methodName + " on " + clazz);
-        }
-        final MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
-
-        // see if we can find the method directly
-        // most of the time this works and it's much faster
-        try {
-            // Check the cache first
-            Method method = getCachedMethod(md);
-            if (method != null) {
-                return method;
-            }
-
-            method = clazz.getMethod(methodName, parameterTypes);
-            if (LOG.isTraceEnabled()) {
-                LOG.trace("Found straight match: " + method);
-                LOG.trace("isPublic:" + Modifier.isPublic(method.getModifiers()));
-            }
-
-            setMethodAccessible(method); // Default access superclass workaround
-
-            cacheMethod(md, method);
-            return method;
-
-        } catch (final NoSuchMethodException e) { /* SWALLOW */ }
-
-        // search through all methods
-        final int paramSize = parameterTypes.length;
-        Method bestMatch = null;
-        final Method[] methods = clazz.getMethods();
-        float bestMatchCost = Float.MAX_VALUE;
-        float myCost = Float.MAX_VALUE;
-        for (final Method method2 : methods) {
-            if (method2.getName().equals(methodName)) {
-                // log some trace information
-                if (LOG.isTraceEnabled()) {
-                    LOG.trace("Found matching name:");
-                    LOG.trace(method2);
-                }
-
-                // compare parameters
-                final Class<?>[] methodsParams = method2.getParameterTypes();
-                final int methodParamSize = methodsParams.length;
-                if (methodParamSize == paramSize) {
-                    boolean match = true;
-                    for (int n = 0 ; n < methodParamSize; n++) {
-                        if (LOG.isTraceEnabled()) {
-                            LOG.trace("Param=" + parameterTypes[n].getName());
-                            LOG.trace("Method=" + methodsParams[n].getName());
-                        }
-                        if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
-                            if (LOG.isTraceEnabled()) {
-                                LOG.trace(methodsParams[n] + " is not assignable from "
-                                            + parameterTypes[n]);
-                            }
-                            match = false;
-                            break;
-                        }
-                    }
-
-                    if (match) {
-                        // get accessible version of method
-                        final Method method = getAccessibleMethod(clazz, method2);
-                        if (method != null) {
-                            if (LOG.isTraceEnabled()) {
-                                LOG.trace(method + " accessible version of "
-                                            + method2);
-                            }
-                            setMethodAccessible(method); // Default access superclass workaround
-                            myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes());
-                            if ( myCost < bestMatchCost ) {
-                               bestMatch = method;
-                               bestMatchCost = myCost;
-                            }
-                        }
-
-                        LOG.trace("Couldn't find accessible method.");
-                    }
-                }
-            }
-        }
-        if (bestMatch != null) {
-            cacheMethod(md, bestMatch);
-        } else {
-            // didn't find a match
-            LOG.trace("No match found.");
+    public static Object invokeMethod(
+            final Object object,
+            final String methodName,
+            Object[] args)
+            throws
+            NoSuchMethodException,
+            IllegalAccessException,
+            InvocationTargetException {
+        if (args == null) {
+            args = BeanUtils.EMPTY_OBJECT_ARRAY;
         }
-
-        return bestMatch;
+        final int arguments = args.length;
+        final Class<?>[] parameterTypes = new Class[arguments];
+        for (int i = 0; i < arguments; i++) {
+            parameterTypes[i] = args[i].getClass();
+        }
+        return invokeMethod(object, methodName, args, parameterTypes);
     }
 
     /**
-     * Try to make the method accessible
-     * @param method The source arguments
+     * <p>Invoke a named method whose parameter type matches the object type.</p>
+     *
+     * <p>The behavior of this method is less deterministic
+     * than {@link
+     * #invokeExactMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
+     * It loops through all methods with names that match
+     * and then executes the first it finds with compatible parameters.</p>
+     *
+     * <p>This method supports calls to methods taking primitive parameters
+     * via passing in wrapping classes. So, for example, a {@code Boolean} class
+     * would match a {@code boolean} primitive.</p>
+     *
+     *
+     * @param object invoke method on this object
+     * @param methodName get method with this name
+     * @param args use these arguments - treat null as empty array (passing null will
+     *  result in calling the parameterless method with name {@code methodName}).
+     * @param parameterTypes match these parameters - treat null as empty array
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
      */
-    private static void setMethodAccessible(final Method method) {
-        try {
-            //
-            // XXX Default access superclass workaround
-            //
-            // When a public class has a default access superclass
-            // with public methods, these methods are accessible.
-            // Calling them from compiled code works fine.
-            //
-            // Unfortunately, using reflection to invoke these methods
-            // seems to (wrongly) to prevent access even when the method
-            // modifier is public.
-            //
-            // The following workaround solves the problem but will only
-            // work from sufficiently privileges code.
-            //
-            // Better workarounds would be gratefully accepted.
-            //
-            if (!method.isAccessible()) {
-                method.setAccessible(true);
-            }
-
-        } catch (final SecurityException se) {
-            // log but continue just in case the method.invoke works anyway
-            if (!loggedAccessibleWarning) {
-                boolean vulnerableJVM = false;
-                try {
-                    final String specVersion = System.getProperty("java.specification.version");
-                    if (specVersion.charAt(0) == '1' &&
-                            (specVersion.charAt(2) == '0' ||
-                             specVersion.charAt(2) == '1' ||
-                             specVersion.charAt(2) == '2' ||
-                             specVersion.charAt(2) == '3')) {
+    public static Object invokeMethod(
+            final Object object,
+            final String methodName,
+            Object[] args,
+            Class<?>[] parameterTypes)
+                throws
+                    NoSuchMethodException,
+                    IllegalAccessException,
+                    InvocationTargetException {
+        if (parameterTypes == null) {
+            parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
+        }
+        if (args == null) {
+            args = BeanUtils.EMPTY_OBJECT_ARRAY;
+        }
 
-                        vulnerableJVM = true;
-                    }
-                } catch (final SecurityException e) {
-                    // don't know - so display warning
-                    vulnerableJVM = true;
-                }
-                if (vulnerableJVM) {
-                    LOG.warn(
-                        "Current Security Manager restricts use of workarounds for reflection bugs "
-                        + " in pre-1.4 JVMs.");
-                }
-                loggedAccessibleWarning = true;
-            }
-            LOG.debug("Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", se);
+        final Method method = getMatchingAccessibleMethod(
+                object.getClass(),
+                methodName,
+                parameterTypes);
+        if (method == null) {
+            throw new NoSuchMethodException("No such accessible method: " +
+                    methodName + "() on object: " + object.getClass().getName());
         }
+        return method.invoke(object, args);
     }
 
     /**
-     * Returns the sum of the object transformation cost for each class in the source
-     * argument list.
-     * @param srcArgs The source arguments
-     * @param destArgs The destination arguments
-     * @return The total transformation cost
+     * <p>Invoke a named static method whose parameter type matches the object type.</p>
+     *
+     * <p>The behavior of this method is less deterministic
+     * than {@link #invokeExactMethod(Object, String, Object[], Class[])}.
+     * It loops through all methods with names that match
+     * and then executes the first it finds with compatible parameters.</p>
+     *
+     * <p>This method supports calls to methods taking primitive parameters
+     * via passing in wrapping classes. So, for example, a {@code Boolean} class
+     * would match a {@code boolean} primitive.</p>
+     *
+     * <p> This is a convenient wrapper for
+     * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}.
+     * </p>
+     *
+     * @param objectClass invoke static method on this class
+     * @param methodName get method with this name
+     * @param arg use this argument. May be null (this will result in calling the
+     *  parameterless method with name {@code methodName}).
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
+     * @since 1.8.0
      */
-    private static float getTotalTransformationCost(final Class<?>[] srcArgs, final Class<?>[] destArgs) {
-        float totalCost = 0.0f;
-        for (int i = 0; i < srcArgs.length; i++) {
-            Class<?> srcClass, destClass;
-            srcClass = srcArgs[i];
-            destClass = destArgs[i];
-            totalCost += getObjectTransformationCost(srcClass, destClass);
-        }
-
-        return totalCost;
+    public static Object invokeStaticMethod(
+            final Class<?> objectClass,
+            final String methodName,
+            final Object arg)
+            throws
+            NoSuchMethodException,
+            IllegalAccessException,
+            InvocationTargetException {
+        final Object[] args = toArray(arg);
+        return invokeStaticMethod (objectClass, methodName, args);
     }
 
     /**
-     * Gets the number of steps required needed to turn the source class into the
-     * destination class. This represents the number of steps in the object hierarchy
-     * graph.
-     * @param srcClass The source class
-     * @param destClass The destination class
-     * @return The cost of transforming an object
+     * <p>Invoke a named static method whose parameter type matches the object type.</p>
+     *
+     * <p>The behavior of this method is less deterministic
+     * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+     * It loops through all methods with names that match
+     * and then executes the first it finds with compatible parameters.</p>
+     *
+     * <p>This method supports calls to methods taking primitive parameters
+     * via passing in wrapping classes. So, for example, a {@code Boolean} class
+     * would match a {@code boolean} primitive.</p>
+     *
+     * <p> This is a convenient wrapper for
+     * {@link #invokeStaticMethod(Class objectClass, String methodName, Object[] args, Class[] parameterTypes)}.
+     * </p>
+     *
+     * @param objectClass invoke static method on this class
+     * @param methodName get method with this name
+     * @param args use these arguments - treat null as empty array (passing null will
+     *  result in calling the parameterless method with name {@code methodName}).
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
+     * @since 1.8.0
      */
-    private static float getObjectTransformationCost(Class<?> srcClass, final Class<?> destClass) {
-        float cost = 0.0f;
-        while (srcClass != null && !destClass.equals(srcClass)) {
-            if (destClass.isPrimitive()) {
-                final Class<?> destClassWrapperClazz = getPrimitiveWrapper(destClass);
-                if (destClassWrapperClazz != null && destClassWrapperClazz.equals(srcClass)) {
-                    cost += 0.25f;
-                    break;
-                }
-            }
-            if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) {
-                // slight penalty for interface match.
-                // we still want an exact match to override an interface match, but
-                // an interface match should override anything where we have to get a
-                // superclass.
-                cost += 0.25f;
-                break;
-            }
-            cost++;
-            srcClass = srcClass.getSuperclass();
+    public static Object invokeStaticMethod(
+            final Class<?> objectClass,
+            final String methodName,
+            Object[] args)
+            throws
+            NoSuchMethodException,
+            IllegalAccessException,
+            InvocationTargetException {
+        if (args == null) {
+            args = BeanUtils.EMPTY_OBJECT_ARRAY;
+        }
+        final int arguments = args.length;
+        final Class<?>[] parameterTypes = new Class[arguments];
+        for (int i = 0; i < arguments; i++) {
+            parameterTypes[i] = args[i].getClass();
         }
+        return invokeStaticMethod (objectClass, methodName, args, parameterTypes);
+    }
+
+    /**
+     * <p>Invoke a named static method whose parameter type matches the object type.</p>
+     *
+     * <p>The behavior of this method is less deterministic
+     * than {@link
+     * #invokeExactStaticMethod(Class objectClass, String methodName, Object[] args, Class[] parameterTypes)}.
+     * It loops through all methods with names that match
+     * and then executes the first it finds with compatible parameters.</p>
+     *
+     * <p>This method supports calls to methods taking primitive parameters
+     * via passing in wrapping classes. So, for example, a {@code Boolean} class
+     * would match a {@code boolean} primitive.</p>
+     *
+     *
+     * @param objectClass invoke static method on this class
+     * @param methodName get method with this name
+     * @param args use these arguments - treat null as empty array (passing null will
+     *  result in calling the parameterless method with name {@code methodName}).
+     * @param parameterTypes match these parameters - treat null as empty array
+     * @return The value returned by the invoked method
+     *
+     * @throws NoSuchMethodException if there is no such accessible method
+     * @throws InvocationTargetException wraps an exception thrown by the
+     *  method invoked
+     * @throws IllegalAccessException if the requested method is not accessible
+     *  via reflection
+     * @since 1.8.0
+     */
+    public static Object invokeStaticMethod(
+            final Class<?> objectClass,
+            final String methodName,
+            Object[] args,
+            Class<?>[] parameterTypes)
+                throws
+                    NoSuchMethodException,
+                    IllegalAccessException,
+                    InvocationTargetException {
 
-        /*
-         * If the destination class is null, we've traveled all the way up to
-         * an Object match. We'll penalize this by adding 1.5 to the cost.
-         */
-        if (srcClass == null) {
-            cost += 1.5f;
+        if (parameterTypes == null) {
+            parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
+        }
+        if (args == null) {
+            args = BeanUtils.EMPTY_OBJECT_ARRAY;
         }
 
-        return cost;
+        final Method method = getMatchingAccessibleMethod(
+                objectClass,
+                methodName,
+                parameterTypes);
+        if (method == null) {
+            throw new NoSuchMethodException("No such accessible method: " +
+                    methodName + "() on class: " + objectClass.getName());
+        }
+        return method.invoke(null, args);
     }
 
     /**
@@ -1140,78 +1232,81 @@ public class MethodUtils {
     }
 
     /**
-     * Gets the wrapper object class for the given primitive type class.
-     * For example, passing {@code boolean.class</code> returns <code>Boolean.class}
-     * @param primitiveType the primitive type class for which a match is to be found
-     * @return the wrapper type associated with the given primitive
-     * or null if no match is found
+     * Sets whether methods should be cached for greater performance or not,
+     * default is {@code true}.
+     *
+     * @param cacheMethods {@code true} if methods should be
+     * cached for greater performance, otherwise {@code false}
+     * @since 1.8.0
      */
-    public static Class<?> getPrimitiveWrapper(final Class<?> primitiveType) {
-        // does anyone know a better strategy than comparing names?
-        if (boolean.class.equals(primitiveType)) {
-            return Boolean.class;
-        }
-        if (float.class.equals(primitiveType)) {
-            return Float.class;
-        }
-        if (long.class.equals(primitiveType)) {
-            return Long.class;
-        }
-        if (int.class.equals(primitiveType)) {
-            return Integer.class;
-        }
-        if (short.class.equals(primitiveType)) {
-            return Short.class;
-        }
-        if (byte.class.equals(primitiveType)) {
-            return Byte.class;
-        }
-        if (double.class.equals(primitiveType)) {
-            return Double.class;
-        }
-        if (char.class.equals(primitiveType)) {
-            return Character.class;
+    public static synchronized void setCacheMethods(final boolean cacheMethods) {
+        CACHE_METHODS = cacheMethods;
+        if (!CACHE_METHODS) {
+            clearCache();
         }
-        return null;
     }
 
     /**
-     * Gets the class for the primitive type corresponding to the primitive wrapper class given.
-     * For example, an instance of {@code Boolean.class</code> returns a <code>boolean.class}.
-     * @param wrapperType the
-     * @return the primitive type class corresponding to the given wrapper class,
-     * null if no match is found
+     * Try to make the method accessible
+     * @param method The source arguments
      */
-    public static Class<?> getPrimitiveType(final Class<?> wrapperType) {
-        // does anyone know a better strategy than comparing names?
-        if (Boolean.class.equals(wrapperType)) {
-            return boolean.class;
-        }
-        if (Float.class.equals(wrapperType)) {
-            return float.class;
-        }
-        if (Long.class.equals(wrapperType)) {
-            return long.class;
-        }
-        if (Integer.class.equals(wrapperType)) {
-            return int.class;
-        }
-        if (Short.class.equals(wrapperType)) {
-            return short.class;
-        }
-        if (Byte.class.equals(wrapperType)) {
-            return byte.class;
-        }
-        if (Double.class.equals(wrapperType)) {
-            return double.class;
-        }
-        if (Character.class.equals(wrapperType)) {
-            return char.class;
+    private static void setMethodAccessible(final Method method) {
+        try {
+            //
+            // XXX Default access superclass workaround
+            //
+            // When a public class has a default access superclass
+            // with public methods, these methods are accessible.
+            // Calling them from compiled code works fine.
+            //
+            // Unfortunately, using reflection to invoke these methods
+            // seems to (wrongly) to prevent access even when the method
+            // modifier is public.
+            //
+            // The following workaround solves the problem but will only
+            // work from sufficiently privileges code.
+            //
+            // Better workarounds would be gratefully accepted.
+            //
+            if (!method.isAccessible()) {
+                method.setAccessible(true);
+            }
+
+        } catch (final SecurityException se) {
+            // log but continue just in case the method.invoke works anyway
+            if (!loggedAccessibleWarning) {
+                boolean vulnerableJVM = false;
+                try {
+                    final String specVersion = System.getProperty("java.specification.version");
+                    if (specVersion.charAt(0) == '1' &&
+                            (specVersion.charAt(2) == '0' ||
+                             specVersion.charAt(2) == '1' ||
+                             specVersion.charAt(2) == '2' ||
+                             specVersion.charAt(2) == '3')) {
+
+                        vulnerableJVM = true;
+                    }
+                } catch (final SecurityException e) {
+                    // don't know - so display warning
+                    vulnerableJVM = true;
+                }
+                if (vulnerableJVM) {
+                    LOG.warn(
+                        "Current Security Manager restricts use of workarounds for reflection bugs "
+                        + " in pre-1.4 JVMs.");
+                }
+                loggedAccessibleWarning = true;
+            }
+            LOG.debug("Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", se);
         }
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Not a known primitive wrapper class: " + wrapperType);
+    }
+
+    private static Object[] toArray(final Object arg) {
+        Object[] args = null;
+        if (arg != null) {
+            args = new Object[] { arg };
         }
-        return null;
+        return args;
     }
 
     /**
@@ -1230,99 +1325,4 @@ public class MethodUtils {
         }
         return clazz;
     }
-
-    /**
-     * Gets the method from the cache, if present.
-     *
-     * @param md The method descriptor
-     * @return The cached method
-     */
-    private static Method getCachedMethod(final MethodDescriptor md) {
-        if (CACHE_METHODS) {
-            final Reference<Method> methodRef = cache.get(md);
-            if (methodRef != null) {
-                return methodRef.get();
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Add a method to the cache.
-     *
-     * @param md The method descriptor
-     * @param method The method to cache
-     */
-    private static void cacheMethod(final MethodDescriptor md, final Method method) {
-        if (CACHE_METHODS && method != null) {
-            cache.put(md, new WeakReference<>(method));
-        }
-    }
-
-    /**
-     * Represents the key to looking up a Method by reflection.
-     */
-    private static class MethodDescriptor {
-        private final Class<?> cls;
-        private final String methodName;
-        private final Class<?>[] paramTypes;
-        private final boolean exact;
-        private final int hashCode;
-
-        /**
-         * The sole constructor.
-         *
-         * @param cls  the class to reflect, must not be null
-         * @param methodName  the method name to obtain
-         * @param paramTypes the array of classes representing the parameter types
-         * @param exact whether the match has to be exact.
-         */
-        public MethodDescriptor(final Class<?> cls, final String methodName, Class<?>[] paramTypes,
-                final boolean exact) {
-            if (cls == null) {
-                throw new IllegalArgumentException("Class cannot be null");
-            }
-            if (methodName == null) {
-                throw new IllegalArgumentException("Method Name cannot be null");
-            }
-            if (paramTypes == null) {
-                paramTypes = BeanUtils.EMPTY_CLASS_ARRAY;
-            }
-
-            this.cls = cls;
-            this.methodName = methodName;
-            this.paramTypes = paramTypes;
-            this.exact= exact;
-
-            this.hashCode = methodName.length();
-        }
-        /**
-         * Checks for equality.
-         * @param obj object to be tested for equality
-         * @return true, if the object describes the same Method.
-         */
-        @Override
-        public boolean equals(final Object obj) {
-            if (!(obj instanceof MethodDescriptor)) {
-                return false;
-            }
-            final MethodDescriptor md = (MethodDescriptor)obj;
-
-            return exact == md.exact &&
-            methodName.equals(md.methodName) &&
-            cls.equals(md.cls) &&
-            java.util.Arrays.equals(paramTypes, md.paramTypes);
-        }
-        /**
-         * Returns the string length of method name. I.e. if the
-         * hashcodes are different, the objects are different. If the
-         * hashcodes are the same, need to use the equals method to
-         * determine equality.
-         * @return the string length of method name.
-         */
-        @Override
-        public int hashCode() {
-            return hashCode;
-        }
-    }
 }
diff --git a/src/main/java/org/apache/commons/beanutils2/PropertyUtils.java b/src/main/java/org/apache/commons/beanutils2/PropertyUtils.java
index 081c532f..cb50ec3e 100644
--- a/src/main/java/org/apache/commons/beanutils2/PropertyUtils.java
+++ b/src/main/java/org/apache/commons/beanutils2/PropertyUtils.java
@@ -37,29 +37,6 @@ import java.util.Map;
 
 public class PropertyUtils {
 
-    /**
-     * Clear any cached property descriptors information for all classes
-     * loaded by any class loaders.  This is useful in cases where class
-     * loaders are thrown away to implement class reloading.
-     *
-     * <p>For more details see {@code PropertyUtilsBean}.</p>
-     *
-     * @see PropertyUtilsBean#clearDescriptors
-     */
-    public static void clearDescriptors() {
-        PropertyUtilsBean.getInstance().clearDescriptors();
-    }
-
-    /**
-     * Resets the registered {@link BeanIntrospector} objects to the initial default
-     * state.
-     *
-     * @since 1.9
-     */
-    public static void resetBeanIntrospectors() {
-        PropertyUtilsBean.getInstance().resetBeanIntrospectors();
-    }
-
     /**
      * Adds a {@code BeanIntrospector}. This object is invoked when the
      * property descriptors of a class need to be obtained.
@@ -74,16 +51,16 @@ public class PropertyUtils {
     }
 
     /**
-     * Removes the specified {@code BeanIntrospector}.
+     * Clear any cached property descriptors information for all classes
+     * loaded by any class loaders.  This is useful in cases where class
+     * loaders are thrown away to implement class reloading.
      *
-     * @param introspector the {@code BeanIntrospector} to be removed
-     * @return <b>true</b> if the {@code BeanIntrospector} existed and
-     *         could be removed, <b>false</b> otherwise
-     * @since 1.9
+     * <p>For more details see {@code PropertyUtilsBean}.</p>
+     *
+     * @see PropertyUtilsBean#clearDescriptors
      */
-    public static boolean removeBeanIntrospector(final BeanIntrospector introspector) {
-        return PropertyUtilsBean.getInstance().removeBeanIntrospector(
-                introspector);
+    public static void clearDescriptors() {
+        PropertyUtilsBean.getInstance().clearDescriptors();
     }
 
     /**
@@ -535,17 +512,39 @@ public class PropertyUtils {
         return PropertyUtilsBean.getInstance().isWriteable(bean, name);
     }
 
+    /**
+     * Removes the specified {@code BeanIntrospector}.
+     *
+     * @param introspector the {@code BeanIntrospector} to be removed
+     * @return <b>true</b> if the {@code BeanIntrospector} existed and
+     *         could be removed, <b>false</b> otherwise
+     * @since 1.9
+     */
+    public static boolean removeBeanIntrospector(final BeanIntrospector introspector) {
+        return PropertyUtilsBean.getInstance().removeBeanIntrospector(
+                introspector);
+    }
+
+    /**
+     * Resets the registered {@link BeanIntrospector} objects to the initial default
+     * state.
+     *
+     * @since 1.9
+     */
+    public static void resetBeanIntrospectors() {
+        PropertyUtilsBean.getInstance().resetBeanIntrospectors();
+    }
+
     /**
      * <p>Sets the value of the specified indexed property of the specified
      * bean, with no type conversions.</p>
      *
      * <p>For more details see {@code PropertyUtilsBean}.</p>
      *
-     * @param bean Bean whose property is to be modified
-     * @param name {@code propertyname[index]} of the property value
-     *  to be modified
-     * @param value Value to which the specified property element
-     *  should be set
+     * @param bean Bean whose property is to be set
+     * @param name Simple property name of the property value to be set
+     * @param index Index of the property value to be set
+     * @param value Value to which the indexed property element is to be set
      *
      * @throws IndexOutOfBoundsException if the specified index
      *  is outside the valid range for the underlying property
@@ -560,10 +559,10 @@ public class PropertyUtils {
      * @see PropertyUtilsBean#setIndexedProperty(Object, String, Object)
      */
     public static void setIndexedProperty(final Object bean, final String name,
-                                          final Object value)
+                                          final int index, final Object value)
             throws IllegalAccessException, InvocationTargetException,
             NoSuchMethodException {
-        PropertyUtilsBean.getInstance().setIndexedProperty(bean, name, value);
+        PropertyUtilsBean.getInstance().setIndexedProperty(bean, name, index, value);
     }
 
     /**
@@ -572,10 +571,11 @@ public class PropertyUtils {
      *
      * <p>For more details see {@code PropertyUtilsBean}.</p>
      *
-     * @param bean Bean whose property is to be set
-     * @param name Simple property name of the property value to be set
-     * @param index Index of the property value to be set
-     * @param value Value to which the indexed property element is to be set
+     * @param bean Bean whose property is to be modified
+     * @param name {@code propertyname[index]} of the property value
+     *  to be modified
+     * @param value Value to which the specified property element
+     *  should be set
      *
      * @throws IndexOutOfBoundsException if the specified index
      *  is outside the valid range for the underlying property
@@ -590,10 +590,10 @@ public class PropertyUtils {
      * @see PropertyUtilsBean#setIndexedProperty(Object, String, Object)
      */
     public static void setIndexedProperty(final Object bean, final String name,
-                                          final int index, final Object value)
+                                          final Object value)
             throws IllegalAccessException, InvocationTargetException,
             NoSuchMethodException {
-        PropertyUtilsBean.getInstance().setIndexedProperty(bean, name, index, value);
+        PropertyUtilsBean.getInstance().setIndexedProperty(bean, name, value);
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java b/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java
index 89159c19..076260af 100644
--- a/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java
+++ b/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java
@@ -89,7 +89,8 @@ import org.apache.commons.logging.LogFactory;
  */
 public class PropertyUtilsBean {
 
-    private Resolver resolver = new DefaultResolver();
+    /** Log instance */
+    private static final Log LOG = LogFactory.getLog(PropertyUtilsBean.class);
 
     /**
      * Gets the PropertyUtils bean instance.
@@ -99,15 +100,46 @@ public class PropertyUtilsBean {
         return BeanUtilsBean.getInstance().getPropertyUtils();
     }
 
+    /**
+     * Converts an object to a list of objects. This method is used when dealing
+     * with indexed properties. It assumes that indexed properties are stored as
+     * lists of objects.
+     *
+     * @param obj the object to be converted
+     * @return the resulting list of objects
+     */
+    private static List<Object> toObjectList(final Object obj) {
+        @SuppressWarnings("unchecked")
+        final
+        // indexed properties are stored in lists of objects
+        List<Object> list = (List<Object>) obj;
+        return list;
+    }
+    /**
+     * Converts an object to a map with property values. This method is used
+     * when dealing with mapped properties. It assumes that mapped properties
+     * are stored in a Map&lt;String, Object&gt;.
+     *
+     * @param obj the object to be converted
+     * @return the resulting properties map
+     */
+    private static Map<String, Object> toPropertyMap(final Object obj) {
+        @SuppressWarnings("unchecked")
+        final
+        // mapped properties are stores in maps of type <String, Object>
+        Map<String, Object> map = (Map<String, Object>) obj;
+        return map;
+    }
+
+    private Resolver resolver = new DefaultResolver();
+
     /**
      * The cache of PropertyDescriptor arrays for beans we have already
      * introspected, keyed by the java.lang.Class of this object.
      */
     private final WeakFastHashMap<Class<?>, BeanIntrospectionData> descriptorsCache;
-    private final WeakFastHashMap<Class<?>, Map> mappedDescriptorsCache;
 
-    /** Log instance */
-    private static final Log LOG = LogFactory.getLog(PropertyUtilsBean.class);
+    private final WeakFastHashMap<Class<?>, Map> mappedDescriptorsCache;
 
     /** The list with BeanIntrospector objects. */
     private final List<BeanIntrospector> introspectors;
@@ -122,56 +154,6 @@ public class PropertyUtilsBean {
         resetBeanIntrospectors();
     }
 
-    /**
-     * Gets the configured {@link Resolver} implementation used by BeanUtils.
-     * <p>
-     * The {@link Resolver} handles the <i>property name</i>
-     * expressions and the implementation in use effectively
-     * controls the dialect of the <i>expression language</i>
-     * that BeanUtils recognizes.
-     * <p>
-     * {@link DefaultResolver} is the default implementation used.
-     *
-     * @return resolver The property expression resolver.
-     * @since 1.8.0
-     */
-    public Resolver getResolver() {
-        return resolver;
-    }
-
-    /**
-     * Configure the {@link Resolver} implementation used by BeanUtils.
-     * <p>
-     * The {@link Resolver} handles the <i>property name</i>
-     * expressions and the implementation in use effectively
-     * controls the dialect of the <i>expression language</i>
-     * that BeanUtils recognizes.
-     * <p>
-     * {@link DefaultResolver} is the default implementation used.
-     *
-     * @param resolver The property expression resolver.
-     * @since 1.8.0
-     */
-    public void setResolver(final Resolver resolver) {
-        if (resolver == null) {
-            this.resolver = new DefaultResolver();
-        } else {
-            this.resolver = resolver;
-        }
-    }
-
-    /**
-     * Resets the {@link BeanIntrospector} objects registered at this instance. After this
-     * method was called, only the default {@code BeanIntrospector} is registered.
-     *
-     * @since 1.9
-     */
-    public final void resetBeanIntrospectors() {
-        introspectors.clear();
-        introspectors.add(DefaultBeanIntrospector.INSTANCE);
-        introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
-    }
-
     /**
      * Adds a {@code BeanIntrospector}. This object is invoked when the
      * property descriptors of a class need to be obtained.
@@ -189,18 +171,6 @@ public class PropertyUtilsBean {
         introspectors.add(introspector);
     }
 
-    /**
-     * Removes the specified {@code BeanIntrospector}.
-     *
-     * @param introspector the {@code BeanIntrospector} to be removed
-     * @return <b>true</b> if the {@code BeanIntrospector} existed and
-     *         could be removed, <b>false</b> otherwise
-     * @since 1.9
-     */
-    public boolean removeBeanIntrospector(final BeanIntrospector introspector) {
-        return introspectors.remove(introspector);
-    }
-
     /**
      * Clear any cached property descriptors information for all classes
      * loaded by any class loaders.  This is useful in cases where class
@@ -361,6 +331,27 @@ public class PropertyUtilsBean {
         return description;
     }
 
+    /**
+     * Performs introspection on the specified class. This method invokes all {@code BeanIntrospector} objects that were
+     * added to this instance.
+     *
+     * @param beanClass the class to be inspected
+     * @return a data object with the results of introspection
+     */
+    private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {
+        final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
+
+        for (final BeanIntrospector bi : introspectors) {
+            try {
+                bi.introspect(ictx);
+            } catch (final IntrospectionException iex) {
+                LOG.error("Exception during introspection", iex);
+            }
+        }
+
+        return new BeanIntrospectionData(ictx.getPropertyDescriptors());
+    }
+
     /**
      * Gets the value of the specified indexed property of the specified
      * bean, with no type conversions.  The zero-relative index of the
@@ -526,6 +517,30 @@ public class PropertyUtilsBean {
         }
     }
 
+    /**
+     * Obtains the {@code BeanIntrospectionData} object describing the specified bean
+     * class. This object is looked up in the internal cache. If necessary, introspection
+     * is performed now on the affected bean class, and the results object is created.
+     *
+     * @param beanClass the bean class in question
+     * @return the {@code BeanIntrospectionData} object for this class
+     * @throws IllegalArgumentException if the bean class is <b>null</b>
+     */
+    private BeanIntrospectionData getIntrospectionData(final Class<?> beanClass) {
+        if (beanClass == null) {
+            throw new IllegalArgumentException("No bean class specified");
+        }
+
+        // Look up any cached information for this bean class
+        BeanIntrospectionData data = descriptorsCache.get(beanClass);
+        if (data == null) {
+            data = fetchIntrospectionData(beanClass);
+            descriptorsCache.put(beanClass, data);
+        }
+
+        return data;
+    }
+
     /**
      * Gets the value of the specified mapped property of the
      * specified bean, with no type conversions.  The key of the
@@ -755,51 +770,6 @@ public class PropertyUtilsBean {
         return bean;
     }
 
-    /**
-     * This method is called by getNestedProperty and setNestedProperty to
-     * define what it means to get a property from an object which implements
-     * Map. See setPropertyOfMapBean for more information.
-     *
-     * @param bean Map bean
-     * @param propertyName The property name
-     * @return the property value
-     *
-     * @throws IllegalArgumentException when the propertyName is regarded as
-     * being invalid.
-     *
-     * @throws IllegalAccessException just in case subclasses override this
-     * method to try to access real getter methods and find permission is denied.
-     *
-     * @throws InvocationTargetException just in case subclasses override this
-     * method to try to access real getter methods, and find it throws an
-     * exception when invoked.
-     *
-     * @throws NoSuchMethodException just in case subclasses override this
-     * method to try to access real getter methods, and want to fail if
-     * no simple method is available.
-     * @since 1.8.0
-     */
-    protected Object getPropertyOfMapBean(final Map<?, ?> bean, String propertyName)
-        throws IllegalArgumentException, IllegalAccessException,
-        InvocationTargetException, NoSuchMethodException {
-
-        if (resolver.isMapped(propertyName)) {
-            final String name = resolver.getProperty(propertyName);
-            if (name == null || name.isEmpty()) {
-                propertyName = resolver.getKey(propertyName);
-            }
-        }
-
-        if (resolver.isIndexed(propertyName) ||
-            resolver.isMapped(propertyName)) {
-            throw new IllegalArgumentException(
-                    "Indexed or mapped properties are not supported on"
-                    + " objects of type Map: " + propertyName);
-        }
-
-        return bean.get(propertyName);
-    }
-
     /**
      * Gets the value of the specified property of the specified bean,
      * no matter which property reference format is used, with no
@@ -1003,6 +973,51 @@ public class PropertyUtilsBean {
         return null;
     }
 
+    /**
+     * This method is called by getNestedProperty and setNestedProperty to
+     * define what it means to get a property from an object which implements
+     * Map. See setPropertyOfMapBean for more information.
+     *
+     * @param bean Map bean
+     * @param propertyName The property name
+     * @return the property value
+     *
+     * @throws IllegalArgumentException when the propertyName is regarded as
+     * being invalid.
+     *
+     * @throws IllegalAccessException just in case subclasses override this
+     * method to try to access real getter methods and find permission is denied.
+     *
+     * @throws InvocationTargetException just in case subclasses override this
+     * method to try to access real getter methods, and find it throws an
+     * exception when invoked.
+     *
+     * @throws NoSuchMethodException just in case subclasses override this
+     * method to try to access real getter methods, and want to fail if
+     * no simple method is available.
+     * @since 1.8.0
+     */
+    protected Object getPropertyOfMapBean(final Map<?, ?> bean, String propertyName)
+        throws IllegalArgumentException, IllegalAccessException,
+        InvocationTargetException, NoSuchMethodException {
+
+        if (resolver.isMapped(propertyName)) {
+            final String name = resolver.getProperty(propertyName);
+            if (name == null || name.isEmpty()) {
+                propertyName = resolver.getKey(propertyName);
+            }
+        }
+
+        if (resolver.isIndexed(propertyName) ||
+            resolver.isMapped(propertyName)) {
+            throw new IllegalArgumentException(
+                    "Indexed or mapped properties are not supported on"
+                    + " objects of type Map: " + propertyName);
+        }
+
+        return bean.get(propertyName);
+    }
+
     /**
      * Gets the Java Class representing the property type of the specified
      * property, or {@code null} if there is no such property for the
@@ -1101,11 +1116,12 @@ public class PropertyUtilsBean {
      *
      * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
      *
+     * @param clazz The class of the read method will be invoked on
      * @param descriptor Property descriptor to return a getter for
      * @return The read method
      */
-    public Method getReadMethod(final PropertyDescriptor descriptor) {
-        return MethodUtils.getAccessibleMethod(descriptor.getReadMethod());
+    Method getReadMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
+        return MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod());
     }
 
     /**
@@ -1114,24 +1130,40 @@ public class PropertyUtilsBean {
      *
      * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
      *
-     * @param clazz The class of the read method will be invoked on
      * @param descriptor Property descriptor to return a getter for
      * @return The read method
      */
-    Method getReadMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
-        return MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod());
+    public Method getReadMethod(final PropertyDescriptor descriptor) {
+        return MethodUtils.getAccessibleMethod(descriptor.getReadMethod());
     }
 
     /**
-     * Gets the value of the specified simple property of the specified
-     * bean, with no type conversions.
-     *
-     * @param bean Bean whose property is to be extracted
-     * @param name Name of the property to be extracted
-     * @return The property value
-     *
-     * @throws IllegalAccessException if the caller does not have
-     *  access to the property accessor method
+     * Gets the configured {@link Resolver} implementation used by BeanUtils.
+     * <p>
+     * The {@link Resolver} handles the <i>property name</i>
+     * expressions and the implementation in use effectively
+     * controls the dialect of the <i>expression language</i>
+     * that BeanUtils recognizes.
+     * <p>
+     * {@link DefaultResolver} is the default implementation used.
+     *
+     * @return resolver The property expression resolver.
+     * @since 1.8.0
+     */
+    public Resolver getResolver() {
+        return resolver;
+    }
+
+    /**
+     * Gets the value of the specified simple property of the specified
+     * bean, with no type conversions.
+     *
+     * @param bean Bean whose property is to be extracted
+     * @param name Name of the property to be extracted
+     * @return The property value
+     *
+     * @throws IllegalAccessException if the caller does not have
+     *  access to the property accessor method
      * @throws IllegalArgumentException if {@code bean} or
      *  {@code name} is null
      * @throws IllegalArgumentException if the property name
@@ -1202,35 +1234,94 @@ public class PropertyUtilsBean {
      * <p>Return an accessible property setter method for this property,
      * if there is one; otherwise return {@code null}.</p>
      *
-     * <p><em>Note:</em> This method does not work correctly with custom bean
-     * introspection under certain circumstances. It may return {@code null}
-     * even if a write method is defined for the property in question. Use
-     * {@link #getWriteMethod(Class, PropertyDescriptor)} to be sure that the
-     * correct result is returned.</p>
      * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
      *
+     * @param clazz The class of the read method will be invoked on
      * @param descriptor Property descriptor to return a setter for
      * @return The write method
+     * @since 1.9.1
      */
-    public Method getWriteMethod(final PropertyDescriptor descriptor) {
-        return MethodUtils.getAccessibleMethod(descriptor.getWriteMethod());
+    public Method getWriteMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
+        final BeanIntrospectionData data = getIntrospectionData(clazz);
+        return MethodUtils.getAccessibleMethod(clazz,
+                data.getWriteMethod(clazz, descriptor));
     }
 
     /**
      * <p>Return an accessible property setter method for this property,
      * if there is one; otherwise return {@code null}.</p>
      *
+     * <p><em>Note:</em> This method does not work correctly with custom bean
+     * introspection under certain circumstances. It may return {@code null}
+     * even if a write method is defined for the property in question. Use
+     * {@link #getWriteMethod(Class, PropertyDescriptor)} to be sure that the
+     * correct result is returned.</p>
      * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
      *
-     * @param clazz The class of the read method will be invoked on
      * @param descriptor Property descriptor to return a setter for
      * @return The write method
-     * @since 1.9.1
      */
-    public Method getWriteMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
-        final BeanIntrospectionData data = getIntrospectionData(clazz);
-        return MethodUtils.getAccessibleMethod(clazz,
-                data.getWriteMethod(clazz, descriptor));
+    public Method getWriteMethod(final PropertyDescriptor descriptor) {
+        return MethodUtils.getAccessibleMethod(descriptor.getWriteMethod());
+    }
+
+    /** This just catches and wraps IllegalArgumentException. */
+    private Object invokeMethod(
+                        final Method method,
+                        final Object bean,
+                        final Object[] values)
+                            throws
+                                IllegalAccessException,
+                                InvocationTargetException {
+        if (bean == null) {
+            throw new IllegalArgumentException("No bean specified " +
+                "- this should have been checked before reaching this method");
+        }
+
+        try {
+
+            return method.invoke(bean, values);
+
+        } catch (final NullPointerException | IllegalArgumentException cause) {
+            // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is
+            // null for a primitive value (JDK 1.5+ throw IllegalArgumentException)
+            final StringBuilder valueString = new StringBuilder();
+            if (values != null) {
+                for (int i = 0; i < values.length; i++) {
+                    if (i>0) {
+                        valueString.append(", ");
+                    }
+                    if (values[i] == null) {
+                        valueString.append("<null>");
+                    } else {
+                        valueString.append(values[i].getClass().getName());
+                    }
+                }
+            }
+            final StringBuilder expectedString = new StringBuilder();
+            final Class<?>[] parTypes = method.getParameterTypes();
+            if (parTypes != null) {
+                for (int i = 0; i < parTypes.length; i++) {
+                    if (i > 0) {
+                        expectedString.append(", ");
+                    }
+                    expectedString.append(parTypes[i].getName());
+                }
+            }
+            final IllegalArgumentException e = new IllegalArgumentException(
+                "Cannot invoke " + method.getDeclaringClass().getName() + "."
+                + method.getName() + " on bean class '" + bean.getClass() +
+                "' - " + cause.getMessage()
+                // as per https://issues.apache.org/jira/browse/BEANUTILS-224
+                + " - had objects of type \"" + valueString
+                + "\" but expected signature \""
+                +   expectedString + "\""
+                );
+            if (!BeanUtils.initCause(e, cause)) {
+                LOG.error("Method invocation failed", cause);
+            }
+            throw e;
+        }
     }
 
     /**
@@ -1390,60 +1481,27 @@ public class PropertyUtilsBean {
     }
 
     /**
-     * Sets the value of the specified indexed property of the specified
-     * bean, with no type conversions.  The zero-relative index of the
-     * required value must be included (in square brackets) as a suffix to
-     * the property name, or {@code IllegalArgumentException} will be
-     * thrown.  In addition to supporting the JavaBeans specification, this
-     * method has been extended to support {@code List} objects as well.
-     *
-     * @param bean Bean whose property is to be modified
-     * @param name {@code propertyname[index]} of the property value
-     *  to be modified
-     * @param value Value to which the specified property element
-     *  should be set
+     * Removes the specified {@code BeanIntrospector}.
      *
-     * @throws IndexOutOfBoundsException if the specified index
-     *  is outside the valid range for the underlying property
-     * @throws IllegalAccessException if the caller does not have
-     *  access to the property accessor method
-     * @throws IllegalArgumentException if {@code bean} or
-     *  {@code name} is null
-     * @throws InvocationTargetException if the property accessor method
-     *  throws an exception
-     * @throws NoSuchMethodException if an accessor method for this
-     *  property cannot be found
+     * @param introspector the {@code BeanIntrospector} to be removed
+     * @return <b>true</b> if the {@code BeanIntrospector} existed and
+     *         could be removed, <b>false</b> otherwise
+     * @since 1.9
      */
-    public void setIndexedProperty(final Object bean, String name,
-                                          final Object value)
-            throws IllegalAccessException, InvocationTargetException,
-            NoSuchMethodException {
-        if (bean == null) {
-            throw new IllegalArgumentException("No bean specified");
-        }
-        if (name == null) {
-            throw new IllegalArgumentException("No name specified for bean class '" +
-                    bean.getClass() + "'");
-        }
-
-        // Identify the index of the requested individual property
-        int index = -1;
-        try {
-            index = resolver.getIndex(name);
-        } catch (final IllegalArgumentException e) {
-            throw new IllegalArgumentException("Invalid indexed property '" +
-                    name + "' on bean class '" + bean.getClass() + "'");
-        }
-        if (index < 0) {
-            throw new IllegalArgumentException("Invalid indexed property '" +
-                    name + "' on bean class '" + bean.getClass() + "'");
-        }
-
-        // Isolate the name
-        name = resolver.getProperty(name);
+    public boolean removeBeanIntrospector(final BeanIntrospector introspector) {
+        return introspectors.remove(introspector);
+    }
 
-        // Set the specified indexed property value
-        setIndexedProperty(bean, name, index, value);
+    /**
+     * Resets the {@link BeanIntrospector} objects registered at this instance. After this
+     * method was called, only the default {@code BeanIntrospector} is registered.
+     *
+     * @since 1.9
+     */
+    public final void resetBeanIntrospectors() {
+        introspectors.clear();
+        introspectors.add(DefaultBeanIntrospector.INSTANCE);
+        introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
     }
 
     /**
@@ -1566,6 +1624,63 @@ public class PropertyUtilsBean {
         }
     }
 
+    /**
+     * Sets the value of the specified indexed property of the specified
+     * bean, with no type conversions.  The zero-relative index of the
+     * required value must be included (in square brackets) as a suffix to
+     * the property name, or {@code IllegalArgumentException} will be
+     * thrown.  In addition to supporting the JavaBeans specification, this
+     * method has been extended to support {@code List} objects as well.
+     *
+     * @param bean Bean whose property is to be modified
+     * @param name {@code propertyname[index]} of the property value
+     *  to be modified
+     * @param value Value to which the specified property element
+     *  should be set
+     *
+     * @throws IndexOutOfBoundsException if the specified index
+     *  is outside the valid range for the underlying property
+     * @throws IllegalAccessException if the caller does not have
+     *  access to the property accessor method
+     * @throws IllegalArgumentException if {@code bean} or
+     *  {@code name} is null
+     * @throws InvocationTargetException if the property accessor method
+     *  throws an exception
+     * @throws NoSuchMethodException if an accessor method for this
+     *  property cannot be found
+     */
+    public void setIndexedProperty(final Object bean, String name,
+                                          final Object value)
+            throws IllegalAccessException, InvocationTargetException,
+            NoSuchMethodException {
+        if (bean == null) {
+            throw new IllegalArgumentException("No bean specified");
+        }
+        if (name == null) {
+            throw new IllegalArgumentException("No name specified for bean class '" +
+                    bean.getClass() + "'");
+        }
+
+        // Identify the index of the requested individual property
+        int index = -1;
+        try {
+            index = resolver.getIndex(name);
+        } catch (final IllegalArgumentException e) {
+            throw new IllegalArgumentException("Invalid indexed property '" +
+                    name + "' on bean class '" + bean.getClass() + "'");
+        }
+        if (index < 0) {
+            throw new IllegalArgumentException("Invalid indexed property '" +
+                    name + "' on bean class '" + bean.getClass() + "'");
+        }
+
+        // Isolate the name
+        name = resolver.getProperty(name);
+
+        // Set the specified indexed property value
+        setIndexedProperty(bean, name, index, value);
+    }
+
     /**
      * Sets the value of the specified mapped property of the
      * specified bean, with no type conversions.  The key of the
@@ -1786,6 +1901,31 @@ public class PropertyUtilsBean {
         }
     }
 
+    /**
+     * Sets the value of the specified property of the specified bean,
+     * no matter which property reference format is used, with no
+     * type conversions.
+     *
+     * @param bean Bean whose property is to be modified
+     * @param name Possibly indexed and/or nested name of the property
+     *  to be modified
+     * @param value Value to which this property is to be set
+     *
+     * @throws IllegalAccessException if the caller does not have
+     *  access to the property accessor method
+     * @throws IllegalArgumentException if {@code bean} or
+     *  {@code name} is null
+     * @throws InvocationTargetException if the property accessor method
+     *  throws an exception
+     * @throws NoSuchMethodException if an accessor method for this
+     *  property cannot be found
+     */
+    public void setProperty(final Object bean, final String name, final Object value)
+            throws IllegalAccessException, InvocationTargetException,
+            NoSuchMethodException {
+        setNestedProperty(bean, name, value);
+    }
+
     /**
      * This method is called by method setNestedProperty when the current bean
      * is found to be a Map object, and defines how to deal with setting
@@ -1863,28 +2003,24 @@ public class PropertyUtilsBean {
     }
 
     /**
-     * Sets the value of the specified property of the specified bean,
-     * no matter which property reference format is used, with no
-     * type conversions.
-     *
-     * @param bean Bean whose property is to be modified
-     * @param name Possibly indexed and/or nested name of the property
-     *  to be modified
-     * @param value Value to which this property is to be set
+     * Configure the {@link Resolver} implementation used by BeanUtils.
+     * <p>
+     * The {@link Resolver} handles the <i>property name</i>
+     * expressions and the implementation in use effectively
+     * controls the dialect of the <i>expression language</i>
+     * that BeanUtils recognizes.
+     * <p>
+     * {@link DefaultResolver} is the default implementation used.
      *
-     * @throws IllegalAccessException if the caller does not have
-     *  access to the property accessor method
-     * @throws IllegalArgumentException if {@code bean} or
-     *  {@code name} is null
-     * @throws InvocationTargetException if the property accessor method
-     *  throws an exception
-     * @throws NoSuchMethodException if an accessor method for this
-     *  property cannot be found
+     * @param resolver The property expression resolver.
+     * @since 1.8.0
      */
-    public void setProperty(final Object bean, final String name, final Object value)
-            throws IllegalAccessException, InvocationTargetException,
-            NoSuchMethodException {
-        setNestedProperty(bean, name, value);
+    public void setResolver(final Resolver resolver) {
+        if (resolver == null) {
+            this.resolver = new DefaultResolver();
+        } else {
+            this.resolver = resolver;
+        }
     }
 
     /**
@@ -1958,140 +2094,4 @@ public class PropertyUtilsBean {
         }
         invokeMethod(writeMethod, bean, values);
     }
-
-    /** This just catches and wraps IllegalArgumentException. */
-    private Object invokeMethod(
-                        final Method method,
-                        final Object bean,
-                        final Object[] values)
-                            throws
-                                IllegalAccessException,
-                                InvocationTargetException {
-        if (bean == null) {
-            throw new IllegalArgumentException("No bean specified " +
-                "- this should have been checked before reaching this method");
-        }
-
-        try {
-
-            return method.invoke(bean, values);
-
-        } catch (final NullPointerException | IllegalArgumentException cause) {
-            // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is
-            // null for a primitive value (JDK 1.5+ throw IllegalArgumentException)
-            final StringBuilder valueString = new StringBuilder();
-            if (values != null) {
-                for (int i = 0; i < values.length; i++) {
-                    if (i>0) {
-                        valueString.append(", ");
-                    }
-                    if (values[i] == null) {
-                        valueString.append("<null>");
... 25799 lines suppressed ...