DEV Community

Er. Bhupendra
Er. Bhupendra

Posted on

PART 8 :CONTROLLER ALL CONCEPT IN SPRINGBOOT PROJECT

Generic Type - COMPLETE DEEP DIVE 🎯


< > Ke Andar KYA Hota Hai?

ResponseEntity<User>           // User class
ResponseEntity<List<User>>     // List of Users
ResponseEntity<String>         // String
ResponseEntity<Integer>        // Integer
ResponseEntity<Map<String, Object>>  // Map
ResponseEntity<Void>           // Nothing (empty)
ResponseEntity<?>              // Anything (wildcard)
Enter fullscreen mode Exit fullscreen mode

1️⃣ GENERIC TYPE - What is ?

Definition:

// Generic class definition
public class ResponseEntity<T> {
    private T body;  // T is placeholder for ANY type

    public ResponseEntity(T body) {
        this.body = body;
    }

    public T getBody() {
        return this.body;
    }
}

/*
<T> means:
- T is a TYPE PARAMETER
- T can be replaced with ANY class
- T is decided at USAGE time, not definition time
*/
Enter fullscreen mode Exit fullscreen mode

2️⃣ WHERE is Defined?

In Class Declaration:

// Spring's actual ResponseEntity source code
package org.springframework.http;

public class ResponseEntity<T> extends HttpEntity<T> {
    //                     ↑
    //                  Generic Type Parameter

    private final HttpStatusCode status;

    // Constructor
    public ResponseEntity(T body, HttpStatusCode status) {
        super(body);
        this.status = status;
    }

    // Methods return same type T
    public static <T> ResponseEntity<T> ok(T body) {
        return new ResponseEntity<>(body, HttpStatus.OK);
    }
}
Enter fullscreen mode Exit fullscreen mode

Breakdown:

public class ResponseEntity<T>
             ↑              ↑
             Class name     Generic type parameter

- T is defined at CLASS level
- T can be any type (User, String, List, etc.)
- T is like a variable for types
Enter fullscreen mode Exit fullscreen mode

3️⃣ HOW Does Work?

Example 1: Simple Generic Class

// Define a generic Box class
public class Box<T> {
    private T item;

    public void put(T item) {
        this.item = item;
    }

    public T get() {
        return this.item;
    }
}

// USAGE:

// Box of String
Box<String> stringBox = new Box<>();
stringBox.put("Hello");
String value = stringBox.get();  // Returns String

// Box of Integer
Box<Integer> intBox = new Box<>();
intBox.put(123);
Integer number = intBox.get();  // Returns Integer

// Box of User
Box<User> userBox = new Box<>();
userBox.put(new User("Raj"));
User user = userBox.get();  // Returns User
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

// When you write: Box<String>
// Compiler generates (conceptually):
public class Box_String {
    private String item;

    public void put(String item) {
        this.item = item;
    }

    public String get() {
        return this.item;
    }
}

// When you write: Box<User>
// Compiler generates (conceptually):
public class Box_User {
    private User item;

    public void put(User item) {
        this.item = item;
    }

    public User get() {
        return this.item;
    }
}
Enter fullscreen mode Exit fullscreen mode

4️⃣ ResponseEntity - Real Example

When You Write:

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    return ResponseEntity.ok(user);
}
Enter fullscreen mode Exit fullscreen mode

What Happens:

// Step 1: You declare return type
ResponseEntity<User>
//             ↑
//          T = User

// Step 2: Compiler knows
// - body type is User
// - ok() method should accept User
// - getBody() will return User

// Step 3: At runtime
ResponseEntity<User> response = ResponseEntity.ok(user);
User retrievedUser = response.getBody();  // Type-safe!
Enter fullscreen mode Exit fullscreen mode

5️⃣ WHERE Can Be Used?

1. Class Level:

public class Container<T> {
    private T data;
}
Enter fullscreen mode Exit fullscreen mode

2. Method Level:

public class Utils {

    // Generic method
    public static <T> T getFirst(List<T> list) {
        return list.isEmpty() ? null : list.get(0);
    }
}

// Usage:
List<String> names = Arrays.asList("Raj", "Amit");
String first = Utils.getFirst(names);  // Returns String

List<Integer> numbers = Arrays.asList(1, 2, 3);
Integer firstNum = Utils.getFirst(numbers);  // Returns Integer
Enter fullscreen mode Exit fullscreen mode

3. Interface Level:

public interface Repository<T> {
    T save(T entity);
    T findById(Long id);
    List<T> findAll();
}

// Implementation:
public class UserRepository implements Repository<User> {
    @Override
    public User save(User entity) { ... }

    @Override
    public User findById(Long id) { ... }

    @Override
    public List<User> findAll() { ... }
}
Enter fullscreen mode Exit fullscreen mode

6️⃣ Common Types in Spring Boot

Built-in Java Types:

ResponseEntity<String>          // String
ResponseEntity<Integer>         // Integer
ResponseEntity<Long>            // Long
ResponseEntity<Boolean>         // Boolean
ResponseEntity<Double>          // Double
Enter fullscreen mode Exit fullscreen mode

Collections:

ResponseEntity<List<User>>              // List
ResponseEntity<Set<String>>             // Set
ResponseEntity<Map<String, Object>>     // Map
ResponseEntity<Collection<Product>>     // Collection
Enter fullscreen mode Exit fullscreen mode

Custom Classes:

ResponseEntity<User>            // Your User class
ResponseEntity<Product>         // Your Product class
ResponseEntity<Order>           // Your Order class
ResponseEntity<ApiResponse<User>>  // Wrapper class
Enter fullscreen mode Exit fullscreen mode

Spring Data Types:

ResponseEntity<Page<User>>      // Paginated data
ResponseEntity<Slice<User>>     // Slice of data
Enter fullscreen mode Exit fullscreen mode

Special Types:

ResponseEntity<Void>            // No body (empty response)
ResponseEntity<?>               // Wildcard (any type)
ResponseEntity<byte[]>          // Binary data
ResponseEntity<Resource>        // File/Resource
Enter fullscreen mode Exit fullscreen mode

7️⃣ Multiple Generic Types

Example: Map with Two Types

// Map has TWO type parameters
Map<K, V>

// Usage:
Map<String, Integer>            // Key=String, Value=Integer
Map<Long, User>                 // Key=Long, Value=User
Map<String, List<Product>>      // Key=String, Value=List<Product>

// In ResponseEntity:
ResponseEntity<Map<String, User>>
Enter fullscreen mode Exit fullscreen mode

Custom Class with Multiple Generics:

public class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }
}

// Usage:
Pair<String, Integer> pair = new Pair<>("age", 25);
String key = pair.getKey();      // Returns String
Integer value = pair.getValue(); // Returns Integer

// In ResponseEntity:
ResponseEntity<Pair<String, User>> response = 
    ResponseEntity.ok(new Pair<>("user", user));
Enter fullscreen mode Exit fullscreen mode

8️⃣ Nested Generics

// List inside ResponseEntity
ResponseEntity<List<User>>

// Map with List value
ResponseEntity<Map<String, List<Product>>>

// Page with custom wrapper
ResponseEntity<ApiResponse<Page<User>>>

// Complex nesting
ResponseEntity<Map<String, List<ApiResponse<User>>>>
Enter fullscreen mode Exit fullscreen mode

Example:

@GetMapping("/data")
public ResponseEntity<ApiResponse<List<User>>> getData() {
    List<User> users = userService.findAll();
    ApiResponse<List<User>> response = ApiResponse.success(users);
    return ResponseEntity.ok(response);
}

// Response structure:
{
  "success": true,
  "data": [
    {"id": 1, "name": "Raj"},
    {"id": 2, "name": "Amit"}
  ]
}
Enter fullscreen mode Exit fullscreen mode

9️⃣ Wildcard Types

1. Unbounded Wildcard: <?>

// Accept any type
public void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

// Can accept:
List<String> strings = Arrays.asList("a", "b");
List<Integer> numbers = Arrays.asList(1, 2, 3);
printList(strings);
printList(numbers);

// In ResponseEntity:
ResponseEntity<?> response = ResponseEntity.ok(anyObject);
Enter fullscreen mode Exit fullscreen mode

2. Upper Bounded Wildcard: <? extends Type>

// Accept Type or its subclasses
public void processAnimals(List<? extends Animal> animals) {
    for (Animal animal : animals) {
        animal.makeSound();
    }
}

// Can accept:
List<Dog> dogs = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
processAnimals(dogs);  // OK
processAnimals(cats);  // OK

// In ResponseEntity:
ResponseEntity<? extends User> response;  // User or subclass
Enter fullscreen mode Exit fullscreen mode

3. Lower Bounded Wildcard: <? super Type>

// Accept Type or its superclasses
public void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}

// Can accept:
List<Integer> integers = new ArrayList<>();
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();
addNumbers(integers);  // OK
addNumbers(numbers);   // OK
addNumbers(objects);   // OK
Enter fullscreen mode Exit fullscreen mode

πŸ”Ÿ Type Erasure - What Happens at Runtime

At Compile Time:

List<String> strings = new ArrayList<>();
strings.add("Hello");
String value = strings.get(0);  // Type-safe
Enter fullscreen mode Exit fullscreen mode

At Runtime (After Type Erasure):

// Generic types removed
List strings = new ArrayList();
strings.add("Hello");
Object value = strings.get(0);  // No type info
String str = (String) value;     // Manual cast
Enter fullscreen mode Exit fullscreen mode

Why Type Erasure?

1. Backward compatibility with old Java code
2. Generics = compile-time feature only
3. At runtime, List<String> = List<Integer> = List
4. JVM doesn't know about <T>
Enter fullscreen mode Exit fullscreen mode

Impact:

// ❌ CANNOT do this
new T()                    // Cannot instantiate
T[] array = new T[10]      // Cannot create array
if (obj instanceof T)      // Cannot check type

// βœ… CAN do this
Class<T> clazz = ...
T instance = clazz.newInstance()  // Using reflection
Enter fullscreen mode Exit fullscreen mode

1️⃣1️⃣ Real-World Examples

Example 1: Generic Service

public interface CrudService<T, ID> {
    T save(T entity);
    T findById(ID id);
    List<T> findAll();
    void delete(ID id);
}

public class UserService implements CrudService<User, Long> {
    //                                           ↑     ↑
    //                                        T=User  ID=Long

    @Override
    public User save(User entity) { ... }

    @Override
    public User findById(Long id) { ... }

    @Override
    public List<User> findAll() { ... }

    @Override
    public void delete(Long id) { ... }
}
Enter fullscreen mode Exit fullscreen mode

Example 2: Generic Repository

public interface JpaRepository<T, ID> extends Repository<T, ID> {
    T save(T entity);
    Optional<T> findById(ID id);
    List<T> findAll();
    void deleteById(ID id);
}

// Usage:
public interface UserRepository extends JpaRepository<User, Long> {
    //                                                  ↑      ↑
    //                                               T=User  ID=Long

    // Inherited methods will use User and Long
    User save(User user);
    Optional<User> findById(Long id);
    List<User> findAll();
}
Enter fullscreen mode Exit fullscreen mode

Example 3: Generic Controller

public abstract class BaseController<T, ID> {

    protected abstract CrudService<T, ID> getService();

    @GetMapping("/{id}")
    public ResponseEntity<T> findById(@PathVariable ID id) {
        T entity = getService().findById(id);
        return ResponseEntity.ok(entity);
    }

    @PostMapping
    public ResponseEntity<T> create(@RequestBody T entity) {
        T created = getService().save(entity);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
}

// Implementation:
@RestController
@RequestMapping("/api/users")
public class UserController extends BaseController<User, Long> {
    //                                                ↑     ↑
    //                                             T=User  ID=Long

    @Autowired
    private UserService userService;

    @Override
    protected CrudService<User, Long> getService() {
        return userService;
    }
}
Enter fullscreen mode Exit fullscreen mode

1️⃣2️⃣ Generic Response Wrappers

ApiResponse with Generic:

public class ApiResponse<T> {
    //                   ↑
    //                Generic data type

    private boolean success;
    private String message;
    private T data;  // Can be any type

    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> response = new ApiResponse<>();
        response.success = true;
        response.data = data;
        return response;
    }
}

// Usage in Controller:

@GetMapping("/user/{id}")
public ResponseEntity<ApiResponse<User>> getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    return ResponseEntity.ok(ApiResponse.success(user));
}
// Response: { "success": true, "data": { "id": 1, ... } }

@GetMapping("/users")
public ResponseEntity<ApiResponse<List<User>>> getUsers() {
    List<User> users = userService.findAll();
    return ResponseEntity.ok(ApiResponse.success(users));
}
// Response: { "success": true, "data": [...] }
Enter fullscreen mode Exit fullscreen mode

PageResponse with Generic:

public class PageResponse<T> {
    private List<T> content;
    private int pageNumber;
    private int totalPages;
    private long totalElements;

    // Constructor, getters, setters
}

// Usage:
@GetMapping("/users")
public ResponseEntity<PageResponse<UserDTO>> getUsers(Pageable pageable) {
    Page<User> page = userService.findAll(pageable);

    List<UserDTO> dtos = page.getContent()
        .stream()
        .map(userMapper::toDTO)
        .collect(Collectors.toList());

    PageResponse<UserDTO> response = new PageResponse<>();
    response.setContent(dtos);
    response.setPageNumber(page.getNumber());
    response.setTotalPages(page.getTotalPages());

    return ResponseEntity.ok(response);
}
Enter fullscreen mode Exit fullscreen mode

1️⃣3️⃣ Where is NOT Allowed?

// ❌ CANNOT use in static context without declaring
public class MyClass<T> {
    private static T data;  // ❌ ERROR

    public static T getData() {  // ❌ ERROR
        return data;
    }
}

// βœ… FIX: Make method generic
public class MyClass<T> {
    public static <E> E getData(E data) {  // βœ… OK
        return data;
    }
}

// ❌ CANNOT instantiate directly
T obj = new T();  // ❌ ERROR

// ❌ CANNOT create generic array
T[] array = new T[10];  // ❌ ERROR

// βœ… FIX: Use List
List<T> list = new ArrayList<>();
Enter fullscreen mode Exit fullscreen mode

πŸ“Š Summary Table

Symbol Meaning Example
<T> Type parameter class Box<T>
<E> Element (collections) List<E>
<K> Key (maps) Map<K, V>
<V> Value (maps) Map<K, V>
<?> Unknown type List<?>
<? extends T> T or subclass List<? extends Number>
<? super T> T or superclass List<? super Integer>

🎯 Key Takeaways

// 1. <T> is defined at CLASS/METHOD level
public class ResponseEntity<T> { ... }
                            ↑
                         Defined here

// 2. <T> is replaced at USAGE time
ResponseEntity<User> response;
                ↑
            T becomes User

// 3. <T> provides TYPE SAFETY
List<String> list = new ArrayList<>();
list.add("Hello");  // βœ… OK
list.add(123);      // ❌ Compile error

// 4. <T> works at COMPILE time only
// At runtime, all generics are erased

// 5. Common patterns:
ResponseEntity<User>              // Single object
ResponseEntity<List<User>>        // List
ResponseEntity<ApiResponse<User>> // Wrapper
ResponseEntity<Void>              // Empty
Enter fullscreen mode Exit fullscreen mode

Generics = Type Safety + Code Reusability! πŸš€

Top comments (0)