Real World Example

A Generic API Response and Cache

Backend APIs (for example a Paytm or Zomato service) wrap every response in the same envelope: status, data and error. A generic ApiResponse<T> works for users, orders or lists, and a generic in-memory cache works for any key and value types.

Example

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

public class Main {
    record User(int id, String name) { }
    record Order(String id, int amount) { }

    public static void main(String[] args) {
        ApiResponse<User> u = ApiResponse.ok(new User(7, "Asha"));
        ApiResponse<List<Order>> orders = ApiResponse.ok(List.of(new Order("O1", 499), new Order("O2", 1299)));
        ApiResponse<User> missing = ApiResponse.error("User 99 not found");
        System.out.println(u);
        System.out.println(orders);
        System.out.println(missing);

        SimpleCache<Integer, User> cache = new SimpleCache<>();
        Function<Integer, User> loadFromDb = id -> {
            System.out.println("  DB query for user " + id);
            return new User(id, "User" + id);
        };
        System.out.println(cache.get(1, loadFromDb));
        System.out.println(cache.get(1, loadFromDb));     // served from cache, no DB query
        System.out.println(cache.get(2, loadFromDb));
    }
}

class ApiResponse<T> {
    private final boolean success;
    private final T data;
    private final String error;

    private ApiResponse(boolean success, T data, String error) {
        this.success = success;
        this.data = data;
        this.error = error;
    }

    static <T> ApiResponse<T> ok(T data) { return new ApiResponse<>(true, data, null); }
    static <T> ApiResponse<T> error(String msg) { return new ApiResponse<>(false, null, msg); }

    @Override
    public String toString() {
        return success ? "{success: true, data: " + data + "}" : "{success: false, error: " + error + "}";
    }
}

class SimpleCache<K, V> {
    private final Map<K, V> store = new LinkedHashMap<>();

    V get(K key, Function<K, V> loader) {
        return store.computeIfAbsent(key, loader);
    }
}

Output

{success: true, data: User[id=7, name=Asha]}
{success: true, data: [Order[id=O1, amount=499], Order[id=O2, amount=1299]]}
{success: false, error: User 99 not found}
  DB query for user 1
User[id=1, name=User1]
User[id=1, name=User1]
  DB query for user 2
User[id=2, name=User2]