June 17, 2026 in code-smells4 minutes
A intermediate-level guide to Data Clumps: before-and-after java code and diagrams for a CS student.
Imagine you are building an e-commerce system. You need to know where to ship a package, so you pass the street, city, and zip code into your shipping service. Later, you realize you also need those same three pieces of information to calculate sales tax. Then, you add a billing module that needs them too.
You notice a pattern: every time you want to talk about a location, you are passing around the exact same three String variables. This is a “Data Clump.” It occurs when a group of related variables travels together through your code across multiple method signatures. It might look like a coincidence, but it is actually a signal that you have discovered an undiscovered domain concept—in this case, an Address.
public class ShippingService {
public void shipOrder(String street, String city, String zipCode, double amount) {
System.out.println("Shipping to " + street + ", " + city + " " + zipCode);
System.out.println("Amount: $" + amount);
}
public void calculateTax(String street, String city, String zipCode, double amount) {
// Tax logic based on location
double tax = (city.equalsIgnoreCase("New York")) ? 0.08 : 0.05;
System.out.println("Tax for " + city + ": $" + (amount * tax));
}
}The current structure creates several problems. First, there is high cognitive load; every time you call these methods, you must remember the specific order of those three strings. Second, it creates “update fragility.” If you decide to add a state or country field later, you have to change every single method signature and every single place in your code where those variables are passed.
classDiagram
class ShippingService {
+shipOrder(String street, String city, String zipCode, double amount)
+calculateTax(String street, String city, String zipCode, double amount)
}
To fix a Data Clump, you use the “Introduce Parameter Object” refactoring. Instead of passing individual primitives (basic types like String or double), you group the related data into a single new class. This uses encapsulation—the practice of bundling data and hiding its internal complexity—to treat the group as one unit.
public record Address(String street, String city, String zipCode) {}
public class ShippingService {
public void shipOrder(Address address, double amount) {
System.out.println("Shipping to " + address.street() + ", " + address.city() + " " + address.zipCode());
System.out.println("Amount: $" + amount);
}
public void calculateTax(Address address, double amount) {
// Tax logic based on location
double tax = (address.city().equalsIgnoreCase("New York")) ? 0.08 : 0.05;
System.out.println("Tax for " + address.city() + ": $" + (amount * tax));
}
}By grouping the street, city, and zip code into an Address object, we have simplified the interface of the ShippingService. The service no longer cares how an address is structured; it just receives a single object that represents a location.
classDiagram
class ShippingService {
+shipOrder(Address address, double amount)
+calculateTax(Address address, double amount)
}
class Address {
+String street
+String city
+String zipCode
}
ShippingService ..> Address : uses
This refactor significantly reduces the “ripple effect” of future changes. If you need to add a country field, you only update the Address class. The method signatures in ShippingService remain exactly the same, preventing a massive cascade of broken code throughout your project.
sequenceDiagram
participant S as ShippingService
participant A as Address
S ->> A: address.city()
A -->> S: "New York"
Introducing Parameter Objects is highly effective when you see three or more variables repeating across different parts of your system. However, be careful not to over-engineer. If you find yourself creating objects for only two related variables that appear in exactly one place, it might be unnecessary overhead.
Furthermore, distinguish between a “Parameter Object” and “Extract Class.” Use Introduce Parameter Object when the primary goal is to clean up method signatures. Use Extract Class when the data clump starts needing its own logic—for example, if you need a method like address.isValid() or address.getFormattedString(). If you keep adding behavior and data into these objects without purpose, you risk creating “God Objects” that try to do too much.
Using the new Parameter Object to pass related data together
Address home = new Address("123 Java Ln", "New York", "10001");
shippingService.shipOrder(home, 99.99);