Stop Writing Utility Classes: Refactor Legacy Java Code with Functional Interfaces
The article explains why traditional static utility classes become hard‑to‑maintain technical debt, and demonstrates how Java 8’s functional interfaces and lambdas can replace them with injectable, testable, composable validation rules, reducing refactoring cost and improving code evolution.
Problem with traditional utility classes
Utility classes such as StringUtils, DateUtil, ValidationUtil, CommonUtils or XXXHelper are often placed under /src/main/java/com/icoderoad/common/util. They start as convenient static methods, but over time they grow to hundreds of lines, become hard to mock in tests, embed business rules that are difficult to replace, and are silently depended on by many modules, creating hidden technical debt.
Typical static validation utility
package com.icoderoad.common.util;
public final class ValidationUtil {
private ValidationUtil() {}
public static boolean isValidEmail(String email) {
return email != null && email.matches("^[\\w.-]+@[\\w.-]+\\.\\w{2,}$");
}
public static boolean isValidPassword(String password) {
return password != null && password.length() >= 8;
}
}Usage:
if (!ValidationUtil.isValidEmail(email)) {
throw new IllegalArgumentException("邮箱格式不合法");
}Issues:
Implementation is fixed; changing a rule requires source changes.
Static methods cannot be mocked, forcing integration tests.
Adding more scenarios inflates the class into a “god‑level” validator.
Functional‑interface based alternative
Java 8 introduced functional interfaces and lambdas, allowing behavior to be passed as parameters instead of being hard‑coded.
Define a generic validator interface
package com.icoderoad.validation;
@FunctionalInterface
public interface Validator<T> {
boolean validate(T value);
}Concrete validator implementations
package com.icoderoad.validation.impl;
import com.icoderoad.validation.Validator;
public class EmailValidator {
public static final Validator<String> DEFAULT =
email -> email != null && email.matches("^[\\w.-]+@[\\w.-]+\\.\\w{2,}$");
} package com.icoderoad.validation.impl;
import com.icoderoad.validation.Validator;
public class PasswordValidator {
public static final Validator<String> MIN_LENGTH_8 =
password -> password != null && password.length() >= 8;
}Business code now receives validators via constructor injection:
package com.icoderoad.service;
import com.icoderoad.validation.Validator;
public class UserService {
private final Validator<String> emailValidator;
private final Validator<String> passwordValidator;
public UserService(Validator<String> emailValidator,
Validator<String> passwordValidator) {
this.emailValidator = emailValidator;
this.passwordValidator = passwordValidator;
}
public void register(String email, String password) {
if (!emailValidator.validate(email)) {
throw new IllegalArgumentException("邮箱格式错误");
}
if (!passwordValidator.validate(password)) {
throw new IllegalArgumentException("密码不符合规则");
}
}
}Resulting capabilities:
Validation rules are injectable.
Unit tests can mock the validators.
Different environments can supply different implementations.
No monolithic utility class.
Inline lambda validators
When a rule is simple, the implementation class can be omitted:
Validator<String> emailValidator = email -> email != null && email.contains("@");
Validator<String> passwordValidator = pwd -> pwd != null && pwd.length() >= 12;This is useful for test code, temporary rules, or A/B experiments.
Comparison of dimensions
Testability: utility class – No; functional interface – Yes.
Replaceability: utility class – No; functional interface – Yes.
Composability: utility class – No; functional interface – Yes.
Evolution cost: utility class – High; functional interface – Low.
Architectural flexibility: utility class – Fixed; functional interface – Extensible.
Conclusion
Utility classes are procedural constructs. When they start to hold validation rules, strategies, or business decisions, refactoring them into functional interfaces makes the code more testable, replaceable, and composable, reducing refactoring cost and enabling smoother evolution of legacy projects.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
LuTiao Programming
LuTiao Programming is a friendly community offering free programming lessons. We inspire learners to explore new ideas and technologies and quickly acquire job-ready skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
