When working with Java domain models or data-transfer objects, the toString() method becomes essential for logging, debugging, and monitoring application behavior. Many teams rely on libraries such as Apache Commons Lang’s ToStringBuilder for structured output, but as classes grow in size, the traditional approach—manually appending each field—quickly becomes repetitive and difficult to maintain.
In this article, we explore a more efficient approach using Java Reflection, highlight its advantages and drawbacks, and compare alternative solutions that may be better suited for modern applications.
Traditional Approach: Manual Field Appending
A classic implementation looks like this:
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this, MyCustomStyle.INSTANCE);
if (StringUtils.isNotBlank(requestId)) {
builder.append("REQUEST_ID", requestId);
}
if (StringUtils.isNotBlank(userId)) {
builder.append("USER_ID", userId);
}
// … dozens of similar lines …
return builder.toString();
}
Drawbacks
- Repetitive and error-prone.
- Hard to maintain: adding or renaming fields requires updating the method manually.
- Large classes generate long, unreadable code blocks.
- Encourages duplication across multiple domain classes.
A Better Solution: Using Reflection in toString()
Reflection allows you to iterate through all fields dynamically, eliminating the need for repetitive code. Here’s a cleaner, more maintainable implementation:
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.JSON_STYLE);
Field[] fields = getClass().getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
Object value = field.get(this);
if (value != null && !value.toString().isBlank()) {
builder.append(field.getName(), value);
}
} catch (IllegalAccessException ignored) {}
}
return builder.toString();
}
Pros of Using Reflection
✅ 1. Significantly Less Boilerplate
You eliminate dozens of if (value != null) blocks.
✅ 2. Automatically Includes New Fields
Any new variable added to the class appears in the log without modifying the method.
✅ 3. Cleaner and More Readable Code
Your class focuses on business logic, not logging maintenance.
✅ 4. Flexible Output Formats
Combined with ToStringStyle, results can be JSON-like, multi-line, or custom.
Cons of Using Reflection
❌ 1. Performance Overhead
Reflection is slower than direct field access.
However, for logging and debugging, the performance cost is usually negligible.
❌ 2. Sensitive Data Exposure
Reflection dumps all fields unless filtered, which may accidentally log:
- passwords
- tokens
- internal IDs
You should explicitly exclude sensitive fields using annotations or naming conventions.
❌ 3. Limited Compile-Time Safety
If a field should not be logged, you must enforce this manually.
❌ 4. Issues in Large Object Graphs
Recursive objects may cause circular references unless guarded.
Safer Alternatives
Below are common alternatives depending on your architecture, performance needs, and security constraints.
1. Lombok @ToString (Most Popular Option)
@ToString(onlyExplicitlyIncluded = true)
public class MyModel {
@ToString.Include
private String requestId;
@ToString.Include
private String status;
}
Pros
- Compile-time generation, zero runtime overhead.
- Exclude fields using
@ToString.Exclude. - Clean and maintainable.
Cons
- Requires Lombok dependency and annotation processing.
- Some teams avoid Lombok for portability reasons.
2. Jackson or Gson Serialization
Serialize an object to JSON for structured logging:
new ObjectMapper().writeValueAsString(this);
Pros
- Produces clean JSON logs.
- Allows inclusion/exclusion via annotations (
@JsonIgnore,@JsonProperty). - Widely used in microservices and observability pipelines.
Cons
- Slightly heavier than manual implementations.
- Requires configuring mappers and modules.
3. Apache Commons ReflectionToStringBuilder
Apache Commons already offers reflection:
ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE);
Pros
- Small change from existing code.
- Very compact and straightforward.
Cons
- Harder to customize field inclusion.
- Performance still slower than manual code.
4. Manually Curated Logging DTO
For high-sensitivity apps (finance, identity, compliance), the safest option is a specific logging DTO:
public LogRecord toLog() {
return new LogRecord(requestId, userId, status);
}
Pros
- Zero risk of leaking confidential fields.
- Clear control over what is logged.
Cons
- Requires explicit maintenance of the DTO.
Which Approach Should You Use?
Use Reflection if:
- You have many DTOs with many fields.
- Performance is not critical for this operation.
- You want minimal maintenance overhead.
Use Lombok if:
- You want compile-time safety and clean code.
- Your team already uses Lombok.
Use JSON Serialization if:
- You want reliably structured logs.
- Your observability tools parse JSON.
Use Manual DTOs if:
- You work with personal data, banking information, or sensitive identifiers.


