Java
Algorithm
Bean
Object Comparison
Software Development

Common algorithm for generating a diff of the fields in two beans?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Generating field-level diffs between two Java beans is a common need in audit logs, change history, and synchronization workflows. A robust diff algorithm should report only meaningful changes and remain stable as models evolve. Reflection-based comparison is a practical baseline when many bean types share the same comparison rules.

Define a Diff Result Model

First define a structured result so downstream code can render logs or API responses consistently.

java
public record FieldDiff(String field, Object oldValue, Object newValue) {}

A dedicated model is better than string concatenation because it keeps data machine-readable.

Reflection-Based Bean Diff

This implementation compares declared fields by name.

java
1import java.lang.reflect.Field;
2import java.util.ArrayList;
3import java.util.List;
4import java.util.Objects;
5
6public class BeanDiff {
7    public static List<FieldDiff> diff(Object left, Object right) {
8        if (left == null || right == null) {
9            throw new IllegalArgumentException("Both objects must be non-null");
10        }
11        if (!left.getClass().equals(right.getClass())) {
12            throw new IllegalArgumentException("Objects must have same type");
13        }
14
15        List<FieldDiff> out = new ArrayList<>();
16        Class<?> type = left.getClass();
17
18        for (Field f : type.getDeclaredFields()) {
19            f.setAccessible(true);
20            try {
21                Object a = f.get(left);
22                Object b = f.get(right);
23                if (!Objects.equals(a, b)) {
24                    out.add(new FieldDiff(f.getName(), a, b));
25                }
26            } catch (IllegalAccessException e) {
27                throw new RuntimeException(e);
28            }
29        }
30        return out;
31    }
32}

This works well for shallow object graphs.

Example Usage

java
1public class UserBean {
2    public String name;
3    public int age;
4    public String role;
5
6    public UserBean(String name, int age, String role) {
7        this.name = name;
8        this.age = age;
9        this.role = role;
10    }
11}
12
13UserBean oldUser = new UserBean("Ava", 30, "admin");
14UserBean newUser = new UserBean("Ava", 31, "owner");
15
16var changes = BeanDiff.diff(oldUser, newUser);
17changes.forEach(System.out::println);

Output contains only modified fields, which is useful for concise audit entries.

Handling Nested Objects

For nested beans, perform recursive diff with dotted field paths like address.city. Add cycle protection using identity sets to avoid infinite recursion on graph structures.

If deep diff is not required, you can compare nested objects by identifier fields only. This reduces complexity and often matches business semantics better.

Excluding Fields and Annotations

Many systems should ignore technical fields such as updatedAt or version. You can skip fields by name set or by annotation.

java
if (f.getName().equals("updatedAt")) {
    continue;
}

Annotation-based exclusion keeps rule definitions close to model classes and reduces duplicated code.

Performance Considerations

Reflection has overhead, especially on large collections. For high-throughput services:

  • cache field metadata per class
  • avoid repeated setAccessible calls where possible
  • compare only fields relevant to business events

In performance-critical paths, generated mappers or explicit comparator code may be faster.

Deterministic Output Ordering

For reliable testing and stable logs, keep diff output ordering deterministic. Reflection order may vary by runtime, so sort field names before comparison when consistency matters.

java
var fields = left.getClass().getDeclaredFields();
java.util.Arrays.sort(fields, java.util.Comparator.comparing(Field::getName));

Stable ordering reduces noisy snapshots in integration tests and makes audit review easier for operators.

Testing Strategy

Create tests for null handling, unchanged objects, one-field changes, and nested object scenarios. Include at least one test with excluded fields to verify policy behavior. A strong test suite is essential because diff code often becomes central to compliance and change history tooling.

Common Pitfalls

  • Comparing objects of different types without explicit error handling.
  • Treating nested objects as simple values when deep comparison is required.
  • Logging all field differences including sensitive data.
  • Using reflection-heavy diffing in hot paths without caching.
  • Ignoring null semantics and domain-specific equality rules.

Summary

  • Bean diffing is useful for audits, synchronization, and change tracking.
  • Reflection offers a quick generic solution for shallow field comparison.
  • Structured diff models are better than free-form log strings.
  • Add exclusion and recursion rules for real-world object models.
  • Optimize metadata handling for high-volume workloads.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms