June 17, 2026 in code-smells4 minutes
A intermediate-level guide to Feature Envy: before-and-after java code and diagrams for a CS student.
Imagine you have a neighbor who doesn’t just wave hello when they pass your house; instead, they lean over the fence to peek through your windows, check if you turned off the stove, and verify that your front door is locked. They are constantly gathering details about your private life just to perform simple tasks that you could easily handle yourself.
In software development, this “nosy neighbor” behavior manifests as a code smell known as Feature Envy. This happens when a method seems more interested in the data of another class than the data of the class it actually belongs to. Instead of asking an object to perform an action, the method pulls out all the internal details using getters and performs the logic itself.
This leads to two major architectural problems:
PayrollService knows exactly how to calculate pay using the internals of Employee, they become tightly intertwined.The following example shows a PayrollService that is overly obsessed with the internal state of an Employee object:
class Employee {
private final double hourlyRate;
private final int hoursWorked;
public Employee(double hourlyRate, int hoursWorked) {
this.hourlyRate = hourlyRate;
this.hoursWorked = hoursWorked;
}
public double getHourlyRate() {
return hourlyRate;
}
public int getHoursWorked() {
return hoursWorked;
}
}
class PayrollService {
// Feature Envy: This method is overly interested in the internal state of Employee,
// performing calculations that should belong to the data owner.
public double calculatePay(Employee employee) {
return employee.getHourlyRate() * employee.getHoursWorked();
}
}
classDiagram
class PayrollService {
+calculatePay(employee) double
}
class Employee {
-hourlyRate: double
-hoursWorked: int
+getHourlyRate() double
+getHoursWorked() int
}
PayrollService ..> Employee : uses getters to access data
To fix Feature Envy, we use a refactoring called Move Method. We identify the logic that is “envious” of another class’s data and move that entire behavior into the class that actually owns that data. In our case, Employee is the “Information Expert”—it knows its own rate and hours, so it should be the one responsible for calculating its pay.
class Employee {
private final double hourlyRate;
private final int hoursWorked;
public Employee(double hourlyRate, int hoursWorked) {
this.hourlyRate = hourlyRate;
this.hoursWorked = hoursWorked;
}
// The logic is moved here to respect encapsulation.
public double calculatePay() {
return this.hourlyRate * this.hoursWorked;
}
}
class PayrollService {
public double processPayroll(Employee employee) {
// Service now delegates the calculation to the expert (the Employee class).
return employee.calculatePay();
}
}By moving the calculation logic into the Employee class, we restore proper encapsulation. Now, if the way we calculate pay changes (for example, adding overtime rules), we only have to modify the Employee class. The PayrollService no longer needs to know how the pay is calculated; it simply asks the expert to do its job.
sequenceDiagram
participant PS as PayrollService
participant E as Employee
PS ->> E: calculatePay()
E ->> E: uses hourlyRate and hoursWorked
E -->> PS: return result
Moving methods is a powerful way to clean up code, but it should be applied with intention.
Use Move Method when:
Avoid Move Method when:
Using the refactored design to calculate pay
Employee emp = new Employee(50.0, 40);
PayrollService service = new PayrollService();
double pay = service.processPayroll(emp); // 2000.0