Java Generics can be used to create reusable code for structured responses in web services. This makes the code easier to maintain and scale. Generics allows the Payload class to be flexible, reusable, and consistent for backend and client-side developers.
Introduction
A well-structured response from a backend service provides clear information about the outcome of the request, any relevant data, any associated messages, and often metadata such as the timestamp.
Structured responses from backend services
A well-structured response from a backend service:
- Provides clarity about the outcome (success or failure).
- Contains any relevant data related to the response.
- May provide any associated message with the response.
- Often includes metadata, like the timestamp.
Using Generics to define a response
Consider the Payload class provided. Here's a breakdown of how it's structured:
- Message: A description of the outcome of the request, such as "Request Successful" or "Item not found."
- Status: A numerical status code, such as 200 for OK or 404 for NOT FOUND.
- Payload: The actual data being sent in the response. This can be of any type, thanks to the use of Generics.
- Timestamp: The time at which the response was created.
In short: The Payload class provides a structured way to represent backend responses, with fields for the message, status, payload, and timestamp.
Using the Builder Pattern
The builder pattern allows us to construct Payload objects in a more expressive way, as shown in the following example:
Payload<String> successPayload = new Payload.Builder<String>()
.message("Request successful")
.status(PayloadStatus.SUCCESS)
.payload("This is the response data")
.build();
Benefits
- Flexible: Payload can be of any type, thanks to Generics.
- Reusable: A single Payload class can be used for many different response types.
- Consistent: All responses have a consistent structure.
Usage
// Create a Payload with a String payload.
Payload<String> successPayload = new Payload.Builder<String>()
.message("Request successful")
.status(PayloadStatus.SUCCESS)
.payload("This is the response data")
.build();
// Create a Payload with a User object payload.
User user = new User("John", "Doe");
Payload<User> userPayload = new Payload.Builder<User>()
.status(PayloadStatus.SUCCESS)
.message("User found")
.payload(user)
.build();
// Create a Payload with a List of Items payload.
List<Item> items = Arrays.asList(new Item("Book"), new Item("Pen"));
Payload<List<Item>> itemsPayload = new Payload.Builder<List<Item>>()
.status(PayloadStatus.SUCCESS)
.message("Items fetched")
.payload(items)
.build();
// Create a Payload with an error message.
Payload<Void> errorPayload = new Payload.Builder<Void>()
.status(PayloadStatus.ERROR)
.message("Something went wrong!")
.build();
Payload Builder Pattern
The Payload Builder Pattern is a design pattern that allows you to create complex objects in a step-by-step manner. It is especially useful for creating objects with many optional parameters.
@Getter
@ToString
public class Payload<T> {
private final String message;
private final int status;
private final T payload;
private final Date timestamp;
private Payload(PayloadStatus status, String message, T payload) {
this.status = status.getCode();
this.message = message;
this.payload = payload;
this.timestamp = Date.from(Instant.now().atZone(ZoneId.of("UTC")).toInstant());
}
public static class Builder<T> {
private String message;
private PayloadStatus status;
private T payload;
public Builder<T> message(String message) {
this.message = message;
return this;
}
public Builder<T> status(PayloadStatus status) {
this.status = status;
return this;
}
public Builder<T> payload(T payload) {
this.payload = payload;
return this;
}
public Payload<T> build() {
return new Payload<>(status, message, payload);
}
}
}
Conclusion
The Payload class with builder pattern is a flexible, reusable, and consistent way to represent responses from a backend service. It is easy to use and can be applied to a variety of use cases.
Top comments (13)
Builder pattern can be used only to optional parameters. Otherwise, it may result in uninitialized values and NPE.
Thanks Sergiy. Yes, it'll result in NPE without optional params. Let me know if we can do something to fix this?
There is a Fluent Builder pattern, suitable for cases when all (or majority) of parameters are required. You may find interesting this article.
Yes, Step Builder will force client to provide datum.
Moreover, this pattern enforces order in which fields are assigned. This is helpful in many situations, especially when changes in code are compared.
Awesome content. We may now implement this pattern similarly in Asp.Net Core. Hope this architecture-related article continues.
Thanks Masum. I'll try to update on this type of article.
Nicely explained. It will help me to learn Builder Patterns. Thanks for the article.
Thanks, Anupam for your feedback. I'll update regularly on this.
This is such an informative article on Builder Patterns! The code snippets shown are pretty good :D and really explains the use of Payload with Builder Pattern.
Thanks, Alanam. I'll try to update regularly on Java Design Pattern's and on Software Architecture.
Nice article.
Lombok has an annotation @builder.
Might be usefull And can reduce boilerplate code
Thanks Pathe. Will try using this from now on.