Core Java

Java Reflection: BeanUtils vs PropertyDescriptor

1. Introduction

Java reflection Beans Property API and Apache Commons BeanUtils are two APIs that provide Beans Property APIs to inspect and manipulate JavaBean objects at runtime. The PropertyDescriptor enables low-level access to properties via getter and setter methods. The Apache Commons BeanUtils simplifies common operations such as reading, writing, populating from maps, and copying bean properties. In this example, we’ll demonstrate both Java reflection beanutils propertydescriptor APIs.

2. Setup

In this step, I will create a simple Java project in Eclipse with JDK 21, Apache Common BeanUtils, and Junit5. Here are the Java source files:

Java Source Files

C:\Users\zzhen\eclipse-workspace\ReflectionBeanApi\src\main\org\zheng\demo>dir
 Volume in drive C is OS
 Volume Serial Number is FE59-88F4

 Directory of C:\Users\zzhen\eclipse-workspace\ReflectionBeanApi\src\main\org\zheng\demo

09/14/2025  08:09 AM              .
09/11/2025  01:22 PM              ..
09/14/2025  11:02 AM               686 ImproperBean.java
09/14/2025  01:24 PM               709 JavaBeanExample.java

Directory of C:\Users\zzhen\eclipse-workspace\ReflectionBeanApi\src\test\org\zheng\demo

09/14/2025  09:22 AM              .
09/11/2025  09:07 PM              ..
09/14/2025  01:06 PM             3,531 TestApacheBeanUtils.java
09/14/2025  11:34 AM             1,077 TestBase.java
09/14/2025  11:17 AM             2,443 TestPropertyDescriptor.java
09/14/2025  11:16 AM             2,214 TestRelectionApi.java

3. Java Beans

A JavaBean is not a special type. It is a design pattern used in frameworks, tools, and libraries. JavaBean is a Java class that follows the following conventions:

  • Public no-argument constructor
    • Allows frameworks (e.g., Spring, Hibernate, BeanUtils) to create instances reflectively.
  • Private fields with public getters and setters
    • Properties must follow naming conventions:
      • isXxx() for boolean.
      • getXxx() / setXxx(…) for all other types.
  • Serializable (optional but common)
    • Implements java.io.Serializable so they can be easily saved, transferred, or cached.

3.1 Base Class without No-Argument Constructor

In this step, I will create a ImproperBean.java without a no-argument constructor.

ImproperBean.java

package org.zheng.demo;

public class ImproperBean {

	private int id;

	private String name;

	private boolean valid;

	//this makes it as improper JavaBean as the default no-args constructor not exists.
	public ImproperBean(int id, boolean valid, String name) {	
		this.id = id;
		this.valid = valid;
		this.name = name;
	}

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public boolean isValid() {
		return valid;
	}

	public void setId(int id) {
		this.id = id;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setValid(boolean valid) {
		this.valid = valid;
	}

}
  • Line 12: The default no-argument constructor is not generated by the compiler as there is a constructor already. The class might start as a JavaBean because the compiler generates default no-argument constructor if there is no any constructor. The best practice for JavaBean is to create a no-argument constructor explicitly.

3.2 Create a Java Bean Example

In this example, I will create the JavaBeanExample.java that extends from ImproperBean. It is a JavaBean as it has the explicitly no-argument constructor.

JavaBeanExample.java

package org.zheng.demo;

import java.io.Serializable;

public class JavaBeanExample extends ImproperBean implements Serializable {

	private static final long serialVersionUID = -8912489223018164443L;

	private String internalField;

	// no-args constructor is added
	public JavaBeanExample() {
		super(0, false, "default");
		this.internalField = "Internal";
	}

	public JavaBeanExample(int id, boolean isValud, String name) {
		super(id, isValud, name);
	}

	@Override
	public String toString() {
		return "JavaBeanExample [internalField=" + internalField + ", getId()=" + getId() + ", getName()=" + getName()
				+ ", isValid()=" + isValid() + "]";
	}

}
  • Line 9: the private internalField is not accessible via either BeanUtils nor PropertyDescriptor as it has no public getter and setter.
  • Line 12: adding the no-argument constructor makes the JavaBeanExample as a JavaBean

4. Access JavaBean via PropertyDescriptor

In this step, I will create a TestPropertyDescriptor.java that accesses and miniplates the JavaBean’s properties.

TestPropertyDescriptor.java

package org.zheng.demo;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.junit.jupiter.api.Test;

class TestPropertyDescriptor extends TestBase {

	@Test
	void get_set_value_via_PropertyDescriptors()
			throws IntrospectionException, IllegalAccessException, InvocationTargetException {

		BeanInfo testBeanInfo = Introspector.getBeanInfo(JavaBeanExample.class);

		for (PropertyDescriptor pd : testBeanInfo.getPropertyDescriptors()) {
			String pdName = pd.getName();
			Method getter = pd.getReadMethod();
			Method setter = pd.getWriteMethod();

			if (getter != null && !"class".equals(pdName)) {
				Object value = getter.invoke(testBean);
				System.out.printf("\nBean Property via Getter: %s=%s", pdName, value);
			}

			if (setter != null) {
				switch (pdName) {
				case ID:
					setter.invoke(testBean, 20);
					break;
				case NAME:
					setter.invoke(testBean, "NewName");
					break;
				case VALID:
					setter.invoke(testBean, true);
					break;
				default:
					System.out.printf("New property added, %s", pdName);
				}
			}

		}

		assertEquals(20, testBean.getId());
		assertEquals("NewName", testBean.getName());
		assertEquals(true, testBean.isValid());
	}

	@Test
	void set_value_via_PropertyDescriptor()
			throws IntrospectionException, IllegalAccessException, InvocationTargetException {
		PropertyDescriptor idPd = new PropertyDescriptor(ID, JavaBeanExample.class);
		idPd.getWriteMethod().invoke(testBean, 22);

		assertEquals(22, testBean.getId());
	}

	@Test
	void throw_exception_wrongType()
			throws IntrospectionException, IllegalAccessException, InvocationTargetException {
		PropertyDescriptor idPd = new PropertyDescriptor(ID, JavaBeanExample.class);
		assertThrows(IllegalArgumentException.class, () -> idPd.getWriteMethod().invoke(testBean, "22"));
	}

	@Test
	void throw_exception_nogetter_field()
			throws IntrospectionException, IllegalAccessException, InvocationTargetException {
		assertThrows(IntrospectionException.class,
				() -> new PropertyDescriptor("internalField", JavaBeanExample.class));
	}

}
  • Line 21: create a BeanInfo instance from Introspector.getBeanInfo(JavaBeanExample.class).
  • Line 23: loop through the PropertyDescriptors.
  • Line 25, 26: obtain the Method for both read and write methods.
  • Line 29: invoke the read methods and return type is Object.
  • Line 36, 39, 42: invoke the write methods to update the properties.
  • Line 59: create a PropertyDescriptor based on the “ID” property in JavaBeanExample.class.
  • Line 69: PropertyDescriptor throws exception when the type mismatches.
  • Line 76: PropertyDescriptor throws exception when the field missed getter nor setter.

Run TestPropertyDescriptor.java as a Junit Test and capture the output.

TestPropertyDescriptor.java

Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=22, getName()=Mary, isValid()=true]Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Bean Property via Getter: id=1
Bean Property via Getter: name=Mary
Bean Property via Getter: valid=true
Updated Bean: JavaBeanExample [internalField=Internal, getId()=20, getName()=NewName, isValid()=true]

5. Access JavaBean via Apache BeanUtils

In this step, I will create a TestApacheBeanUtils.java that accesses and miniplates the JavaBean’s properties.

TestApacheBeanUtils.java

package org.zheng.demo;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.junit.jupiter.api.Test;

class TestApacheBeanUtils extends TestBase {

	@Test
	void copyProperties_from_otherBean()
			throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		JavaBeanExample testBean2 = new JavaBeanExample(2, false, "Zheng");

		BeanUtils.copyProperties(testBean, testBean2);

		// always return String regardless of its type
		assertEquals("2", BeanUtils.getProperty(testBean, ID));
		assertEquals("Zheng", BeanUtils.getProperty(testBean, NAME));
		assertEquals("false", BeanUtils.getProperty(testBean, VALID));

	}

	@Test
	void get_set_value_via_get_setProperty()
			throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		assertEquals("1", BeanUtils.getProperty(testBean, ID));
		assertEquals("Mary", BeanUtils.getProperty(testBean, NAME));
		assertEquals("true", BeanUtils.getProperty(testBean, VALID));

		BeanUtils.setProperty(testBean, ID, 33);
		BeanUtils.setProperty(testBean, NAME, "ZHENG");
		BeanUtils.setProperty(testBean, VALID, false);

		assertEquals("33", BeanUtils.getProperty(testBean, ID));
		assertEquals("ZHENG", BeanUtils.getProperty(testBean, NAME));
		assertEquals("false", BeanUtils.getProperty(testBean, VALID));
	}

	@Test
	void populate_from_map() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		Map<String, Object> values = new HashMap<>();
		values.put(ID, 3);
		values.put(NAME, "Bob");
		values.put(VALID, false);

		BeanUtils.populate(testBean, values);

		assertEquals("3", BeanUtils.getProperty(testBean, ID));
		assertEquals("Bob", BeanUtils.getProperty(testBean, NAME));
		assertEquals("false", BeanUtils.getProperty(testBean, VALID));
	}

	@Test
	void throw_exception_no_getter() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		assertThrows(NoClassDefFoundError.class, () -> BeanUtils.getProperty(testBean, "internalField"));
	}

	@Test
	void throw_exception_unknownField()
			throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		Map<String, Object> values = new HashMap<>();
		values.put(ID, 3);
		values.put("unknownField", 3);

		assertThrows(NoClassDefFoundError.class, () -> BeanUtils.populate(testBean, values));
	}

	@Test
	void throw_exception_map_nogetter()
			throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		Map<String, Object> values = new HashMap<>();
		values.put(ID, 3);
		values.put(NAME, "Bob");
		values.put("internalField", "Oops");

		assertThrows(NoClassDefFoundError.class, () -> BeanUtils.populate(testBean, values));
	}

	@Test
	void ignore_wrongType() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		Map<String, Object> values = new HashMap<>();
		values.put(ID, "33A");
		values.put(NAME, 123);
		testBean = new JavaBeanExample();

		BeanUtils.populate(testBean, values);
		assertEquals("0", BeanUtils.getProperty(testBean, ID));
		assertEquals("123", BeanUtils.getProperty(testBean, NAME));
		assertEquals("false", BeanUtils.getProperty(testBean, VALID));
	}

}
  • Line 20, 23-25: the BeanUtils.copyProperties() method works as expected for JavaBean;
  • Line 36-38: verify the BeanUtils.setProperty() update the property value.
  • Line 52: verify BeanUtils.populate() set the property’s value for a map whose key matches the JavaBean’s properties.
  • Line 61: the BeanUtils.getProperty() throwing exception if the property has no public getter and setter.
  • Line 80, 82: the BeanUtils.populate() throwing exception if the map’s key has no public getter and setter.
  • Line 88, 92, 93: the BeanUtils.populate() ignore if the map’s key value has wrong data type.

Run TestApacheBeanUtils.java as a Junit test and capture the output.

TestApacheBeanUtilsOutput

Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=33, getName()=ZHENG, isValid()=false]Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=3, getName()=Bob, isValid()=false]Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=0, getName()=123, isValid()=false]Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=2, getName()=Zheng, isValid()=false]Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

6. Compare to Java Reflection API

In this step, I will create a TestRelectionApi.java that accesses and miniplates the JavaBean’s properties. Note: java.lang.reflect works for any Java objects.

TestRelectionApi.java

package org.zheng.demo;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.junit.jupiter.api.Test;

class TestRelectionApi extends TestBase {

	@Test
	void getValue_via_Method() throws NoSuchMethodException, SecurityException, IllegalAccessException,
			InvocationTargetException, NoSuchFieldException {

		Method getId = JavaBeanExample.class.getMethod(GET_ID);
		Object value = getId.invoke(testBean);
		assertEquals(1, (int) value);

		Method getName = JavaBeanExample.class.getMethod(GET_NAME);
		value = getName.invoke(testBean);
		assertEquals("Mary", (String) value);

		Method isValid = JavaBeanExample.class.getMethod(IS_VALID);
		value = isValid.invoke(testBean);
		assertEquals(true, (boolean) value);
	}

	@Test
	void get_set_value_via_Field() throws NoSuchMethodException, SecurityException, IllegalAccessException,
			InvocationTargetException, NoSuchFieldException {

		Field internalField = JavaBeanExample.class.getDeclaredField("internalField");
		internalField.setAccessible(true);
		internalField.set(testBean, "Oops");

		assertEquals("Oops", internalField.get(testBean));
	}

	@Test
	void update_via_setMethod()
			throws NoSuchMethodException, SecurityException, IllegalAccessException, InvocationTargetException {

		JavaBeanExample.class.getMethod(SET_ID, int.class).invoke(testBean, 4);
		assertEquals(4, testBean.getId());

		JavaBeanExample.class.getMethod(SET_NAME, String.class).invoke(testBean, "Zheng");
		assertEquals("Zheng", testBean.getName());

		JavaBeanExample.class.getMethod(SET_VALID, boolean.class).invoke(testBean, true);
		assertTrue(testBean.isValid());
	}

	@Test
	void throw_exception_wrongType()
			throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException {
		assertThrows(IllegalArgumentException.class,
				() -> JavaBeanExample.class.getMethod(SET_ID, int.class).invoke(testBean, "123"));
	}

}
  • Line 19: obtain getId method from JavaBeanExample.class.getMethod(GET_ID).
  • Line 20, 21: getId is invoked for testBean and returned value is casted to the int type.
  • Line 23, 24: similar to above, but for the getName method.
  • Line 27, 28: similar to above, but for the isValid method.
  • Line 36,37,38: the reflect API works for any object, any field. so it changed the private internalField.
  • Line 47,50,53: the reflect API works for any fields.
  • Line 61: the reflect set method throw exception when the argument type mismatchs.

Run TestRelectionApi.java as a Junit and capture output

TestRelectionApi Output

Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=4, getName()=Zheng, isValid()=true]Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Oops, getId()=1, getName()=Mary, isValid()=true]Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]Initial Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

Updated Bean: JavaBeanExample [internalField=Internal, getId()=1, getName()=Mary, isValid()=true]

7. TestBase

In this step, I will create a TestBase.java that sets up the common test methods and constants.

TestBase.java

package org.zheng.demo;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

public class TestBase {

	public static final String GET_ID = "getId";
	public static final String GET_NAME = "getName";
	public static final String ID = "id";
	public static final String IS_VALID = "isValid";
	public static final String NAME = "name";
	public static final String SET_ID = "setId";
	public static final String SET_NAME = "setName";
	public static final String SET_VALID = "setValid";
	public static final String VALID = "valid";

	ImproperBean improperBean = null;
	JavaBeanExample testBean = null;

	@AfterEach
	void end() {
		System.out.printf("\nUpdated Bean: %s", testBean.toString());
	}

	@BeforeEach
	void start() {
		testBean = new JavaBeanExample();
		testBean.setId(1);
		testBean.setName("Mary");
		testBean.setValid(true);

		// no longer create it via no-argument constructor
		improperBean = new ImproperBean(2, false, "Zheng");

		System.out.printf("Initial Bean: %s\n", testBean.toString());
	}

}
  • Line 34: the improperBean variable cannot be created via the no-argument constructor.

8. Conclusion

In this example, I created a simple Java program that accesses and miniplates the JavaBean’s properties via Java.beans.PropertyDescriptor and org.apache.commons.beanutils.BeanUtils. The introspection API java.beans is more structured, property-centric API and based on the lower-level java.lang.reflect. I compared them to the lower-level java.lang.reflect API. Note: using the low-level reflect API can expose the sensitive properties uninterionally.

DifferencePropertyDescriptorBeanUtils
Read MethodPropertyDescriptor read method returns type is Object.classBeanUtils.getProperty() returns type is String.class
Write MethodPropertyDescriptor write method throws IllegalArgumentException if the type mismatches.BeanUtils.setProperty() ignores the property if the type mismatches.

9. Download

This was an example of a Java project which accessed and manipulated the JavaBean properties via Java Bean PropertyDescriptor, Apache Common BeanUtils, and Java lang Reflect API.

Download
You can download the full source code of this example here: Java Reflection Beans Property API Example

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button