As a developer, there often comes a time when you need to quickly display your data structures in a human-readable format on the screen. During development, debuggers often provide a nice tree-like view of your data objects, which helps visualize the structure. Building something similar manually to your UI is, of course, possible, but it is too expensive in many situations—especially in rapid application development (RAD) or internal business apps. Today, generative AI can produce decent visualizations, but in the early phases of development, a fully dynamic solution would be more ideal.
In this post, we’ll explore three ways to quickly and effectively pretty-print Java objects with varying levels of effort and customization.
1. Overriding the Object.toString()
Method
The most basic and built-in mechanism in Java for producing a human-readable string representation of an object is the toString()
method, which every class inherits from Object
.
Here’s an excerpt from the toString()
JavaDocs:
"In general, the toString
method returns a string that textually represents this object. The result should be a concise but informative representation that is easy for a person to read. It is recommended that all subclasses override this method."
While this is useful for quick, single-level text-based summaries of your object, it has limitations, especially when working with more large and nested data structures. Dropping every detail of your objects to toString
representation is not concise.
Overriding the toString()
method manually can also be tedious, especially if your class has many fields. Thankfully, modern IDEs and libraries like Apache Commons Lang and Project Lombok provide tools to automatically generate toString()
methods.
For example, if you use Apache Commons Lang, it provides a helpful ToStringBuilder
that often generates “good enough” results with the following oneliner:
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
This approach is perfect when all you need is a quick, single-line summary of your object. However, for multi-level objects, you’ll need something more robust.
2. Pretty-Print with JSON or XML Serialization
For more complex data structures, especially when dealing with multi-level objects, serializing your object into a declarative format like JSON or XML can be a powerful way to visualize your data. Libraries such as Jackson (for JSON), GSON (for JSON), and JAXB (for XML) allow you to easily serialize your Java objects into these formats.
Here’s an example using Jackson to pretty-print an object as JSON:
String prettyJson = new
ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(dto);
System.out.println(prettyJson);
In my Vaadin apps, I often do the same but just drop the prettyJson
to a Pre component on the web UI instead of printing it to System.out
.
While the resulting JSON or XML output is often enough for developers to understand the data structure, I can only suggest using this as a temporary solution while developing the app. While developers are quite accustomed to reading these formats, the presentation is far from optimal for non-technical stakeholders.
This method has the same kind of advantage as the ToStringBuilder.reflectionToString()
in the Apache Commons Lang library. The output automatically reflects any changes you make to your data model. You don’t need to manually update any presentation logic, a major time-saver in early development.
Also, in more complex cases, you’ll most likely need some library-specific hacks to handle things like circular dependencies and “custom” data types, like the modern data and time classes in JDK still need a custom module to be registered with Jakson.
3. Rendering with a GUI Library
For the most polished and user-friendly output, rendering your data with actual UI components is the way to go. This can be as simple as using JavaFX, Swing, or Vaadin class that utilizes standard UI components like tables or read-only form elements.
For example, in Vaadin, you could easily create a read-only form that binds to your Java object (field declarations and class boilerplate omitted):
var formLayout = new FormLayout();
formLayout.add(name, age, email, active);
add(formLayout);
var binder = new Binder<>(Person.class);
binder.bindInstanceFields(this);
binder.setReadOnly(true);
binder.setBean(demoPerson);
The output would be something like this:
While this gives you the freedom to provide the best possible user experience, it has the same downside as overriding toString()
manually: whenever your domain model changes, you must update your UI code, which can be time-consuming when your data model is still evolving. This makes it less ideal for fast-paced development cycles.
Modern AI-powered tools like Vaadin Copilot help by speeding up this process, but if you want to iterate as fast as possible, you’ll want either to build the UI runtime generation using reflection or do some compile-time code generation (could be achieved, for example with Java Annotation Processing). The runtime generation is my favorite of these two, as your changes can even be injected dynamically into a running development server.
The underlying technology for runtime UI generation is Java’s Reflection API. You can dynamically inspect your classes and render a structured UI based on your data.
I recently used this technique to build a helper class called PrettyPrinter
. The solution abuses Jackson’s “internals” (which heavily relies on runtime reflection) as an abstraction to the low-level Reflection API. But instead of JSON, the data is structured with tables, trees, and rich text to create a human-readable presentation of your data. Nested objects are hidden behind Details
components, which expands the more detailed data on demand. Here’s an example:
add(PrettyPrinter.toVaadin(person));
The oneliner above creates a visualization using Vaadin components, which in the demo app look like this:
This approach saves a lot of time, especially during early development when your data model is still in flux. The PrettyPrinter
would still have a lot of low-hanging fruits for improved feature sets, like displaying media files, handling larger collections with lazy loading in a Vaadin Grid, or visualizing a series of numeric data with charts. If you want changes to the default representations, the underlying DtoDisplay has API hooks to customize, e.g., a certain property of a certain kind of object. Join the discussion on Vaadin Forum about the PrettyPrinter
and drop in your ideas.
Conclusion
Whether you’re looking for a quick string representation, a structured JSON/XML serialization, or a dynamic GUI, there’s a pretty-printing solution for every use case. If you need something lightweight and temporary, overriding toString()
or using JSON serialization can help. But if you’re ready for a more sophisticated solution, dynamic UI generation using reflection could save you hours of manual work.