DEV Community

Er. Bhupendra
Er. Bhupendra

Posted on

PART 7 :CONTROLLER ALL CONCEPT IN SPRINGBOOT PROJECT

πŸš€ Controller Return Types - COMPLETE DEEP DIVE


πŸ“Š ALL POSSIBLE RETURN TYPES IN SPRING BOOT CONTROLLER

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Spring Boot Controller - Return Type Options      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                     β”‚
β”‚  1️⃣  Direct Object/Primitive                       β”‚
β”‚  2️⃣  ResponseEntity<T>                             β”‚
β”‚  3️⃣  HttpEntity<T>                                 β”‚
β”‚  4️⃣  String (View Name)                            β”‚
β”‚  5️⃣  void                                          β”‚
β”‚  6️⃣  ModelAndView                                  β”‚
β”‚  7️⃣  @ResponseBody with Object                     β”‚
β”‚  8️⃣  DeferredResult<T> (Async)                     β”‚
β”‚  9️⃣  Callable<T> (Async)                           β”‚
β”‚  πŸ”Ÿ  CompletableFuture<T> (Async)                  β”‚
β”‚  1️⃣1️⃣  Flux<T> / Mono<T> (Reactive)                β”‚
β”‚  1️⃣2️⃣  StreamingResponseBody                        β”‚
β”‚  1️⃣3️⃣  ResponseBodyEmitter                          β”‚
β”‚  1️⃣4️⃣  SseEmitter (Server-Sent Events)             β”‚
β”‚  1️⃣5️⃣  Resource (File Download)                     β”‚
β”‚  1️⃣6️⃣  byte[] / ByteArrayResource                   β”‚
β”‚  1️⃣7️⃣  HttpHeaders                                  β”‚
β”‚  1️⃣8️⃣  Map<String, Object>                          β”‚
β”‚                                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

1️⃣ DIRECT OBJECT RETURN - Simplest

How it Works:

@RestController
@RequestMapping("/api/users")
public class UserController {

    // Direct object return
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }

    // List return
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }

    // Primitive return
    @GetMapping("/count")
    public long getUserCount() {
        return userService.count();
    }

    // String return
    @GetMapping("/message")
    public String getMessage() {
        return "Hello World";
    }
}
Enter fullscreen mode Exit fullscreen mode

What Happens Behind the Scenes:

1. Spring Framework receives your object
   ↓
2. HttpMessageConverter kicks in
   ↓
3. Jackson (default JSON converter) converts object to JSON
   ↓
4. Sets Content-Type: application/json automatically
   ↓
5. Sets Status Code: 200 OK automatically
   ↓
6. Returns response to client
Enter fullscreen mode Exit fullscreen mode

Response:

// User object automatically converted to JSON
{
  "id": 123,
  "name": "Raj",
  "email": "raj@email.com"
}
Enter fullscreen mode Exit fullscreen mode

Limitations:

❌ Cannot control status code (always 200)
❌ Cannot add custom headers
❌ Cannot handle errors properly
❌ No control over response format
Enter fullscreen mode Exit fullscreen mode

2️⃣ ResponseEntity - MOST CONTROL

How it Works:

@RestController
@RequestMapping("/api/users")
public class UserController {

    // Full control over response
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);

        if (user != null) {
            return ResponseEntity
                .ok()  // 200 OK
                .header("X-Custom-Header", "value")
                .body(user);
        } else {
            return ResponseEntity
                .notFound()  // 404 Not Found
                .build();
        }
    }

    // Create with 201 status
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User created = userService.save(user);

        URI location = ServletUriComponentsBuilder
            .fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(created.getId())
            .toUri();

        return ResponseEntity
            .created(location)  // 201 Created
            .body(created);
    }

    // Delete with 204 status
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();  // 204 No Content
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

1. You create ResponseEntity object
   ↓
2. Set status code explicitly (200, 201, 404, etc)
   ↓
3. Set headers if needed
   ↓
4. Set body (optional)
   ↓
5. Spring Framework takes ResponseEntity
   ↓
6. Extracts status, headers, body
   ↓
7. Builds HTTP response
   ↓
8. Returns to client
Enter fullscreen mode Exit fullscreen mode

Class Hierarchy:

// ResponseEntity source code (simplified)
package org.springframework.http;

public class ResponseEntity<T> extends HttpEntity<T> {

    private final HttpStatusCode status;

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

    // Static factory methods
    public static BodyBuilder ok() {
        return status(HttpStatus.OK);
    }

    public static <T> ResponseEntity<T> ok(T body) {
        return ok().body(body);
    }

    public static BodyBuilder created(URI location) {
        return status(HttpStatus.CREATED).location(location);
    }

    // ... more methods
}
Enter fullscreen mode Exit fullscreen mode

Advantages:

βœ… Full control over status code
βœ… Can add custom headers
βœ… Can handle different scenarios (success/error)
βœ… Type-safe with generics
βœ… Industry standard
Enter fullscreen mode Exit fullscreen mode

3️⃣ HttpEntity - ResponseEntity ka Parent

How it Works:

@RestController
public class UserController {

    // HttpEntity - no status code control
    @GetMapping("/users/{id}")
    public HttpEntity<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);

        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Custom-Header", "value");

        return new HttpEntity<>(user, headers);
        // Always returns 200 OK (no status control)
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

// HttpEntity source code (simplified)
package org.springframework.http;

public class HttpEntity<T> {

    private final HttpHeaders headers;
    private final T body;

    public HttpEntity(T body) {
        this(body, null);
    }

    public HttpEntity(T body, HttpHeaders headers) {
        this.body = body;
        this.headers = headers != null ? headers : new HttpHeaders();
    }

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

    public HttpHeaders getHeaders() {
        return this.headers;
    }
}
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… When you need headers but don't care about status
❌ Rarely used (ResponseEntity is better)
Enter fullscreen mode Exit fullscreen mode

4️⃣ String Return - View Name (Thymeleaf/JSP)

How it Works:

@Controller  // NOT @RestController
@RequestMapping("/web")
public class WebController {

    // Returns view name (not JSON)
    @GetMapping("/home")
    public String home(Model model) {
        model.addAttribute("message", "Welcome");
        return "home";  // Looks for home.html in templates/
    }

    // Redirect
    @GetMapping("/redirect")
    public String redirect() {
        return "redirect:/web/home";
    }

    // Forward
    @GetMapping("/forward")
    public String forward() {
        return "forward:/web/home";
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

1. Spring MVC receives String return
   ↓
2. ViewResolver kicks in
   ↓
3. Looks for template: templates/home.html
   ↓
4. Thymeleaf/JSP processes template
   ↓
5. Returns rendered HTML
   ↓
6. Client receives HTML page
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… Traditional web applications (HTML pages)
βœ… Server-side rendering
❌ NOT for REST APIs
Enter fullscreen mode Exit fullscreen mode

5️⃣ void Return - No Response Body

How it Works:

@RestController
public class LogController {

    // void - no response body
    @PostMapping("/log")
    public void logEvent(@RequestBody LogRequest request) {
        logService.log(request);
        // Returns 200 OK with empty body
    }

    // void with @ResponseStatus
    @PostMapping("/event")
    @ResponseStatus(HttpStatus.ACCEPTED)  // 202 Accepted
    public void createEvent(@RequestBody Event event) {
        eventService.create(event);
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

1. Method executes
   ↓
2. No return value
   ↓
3. Spring sets status 200 OK by default
   ↓
4. Response body is empty
   ↓
5. Client receives empty response
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… Fire-and-forget operations
βœ… Logging, auditing
❌ Client doesn't know if operation succeeded
❌ Cannot return data
Enter fullscreen mode Exit fullscreen mode

6️⃣ ModelAndView - MVC Pattern

How it Works:

@Controller
public class ProductController {

    @GetMapping("/products")
    public ModelAndView getProducts() {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("products");  // View name
        mav.addObject("products", productService.findAll());  // Data
        return mav;
    }

    // Alternative
    @GetMapping("/product/{id}")
    public ModelAndView getProduct(@PathVariable Long id) {
        Product product = productService.findById(id);
        return new ModelAndView("product-detail", "product", product);
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

// ModelAndView source code (simplified)
package org.springframework.web.servlet;

public class ModelAndView {

    private Object view;  // View name or View object
    private ModelMap model;  // Data for view
    private HttpStatus status;

    public void setViewName(String viewName) {
        this.view = viewName;
    }

    public void addObject(String name, Object value) {
        getModelMap().addAttribute(name, value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… Traditional Spring MVC
βœ… Server-side rendering
❌ NOT for REST APIs
Enter fullscreen mode Exit fullscreen mode

7️⃣ @ResponseBody with Object - Auto JSON

How it Works:

@Controller  // Regular Controller
@RequestMapping("/api")
public class ApiController {

    @GetMapping("/users")
    @ResponseBody  // Converts to JSON
    public List<User> getUsers() {
        return userService.findAll();
    }

    // @RestController = @Controller + @ResponseBody on all methods
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

@ResponseBody annotation tells Spring:
   ↓
"Don't look for a view, serialize this object to JSON"
   ↓
HttpMessageConverter converts object to JSON
   ↓
Returns JSON response
Enter fullscreen mode Exit fullscreen mode

Note:

@RestController = @Controller + @ResponseBody (on every method)

So these are same:
1. @RestController with no @ResponseBody
2. @Controller with @ResponseBody on each method
Enter fullscreen mode Exit fullscreen mode

8️⃣ DeferredResult - ASYNC Processing

How it Works:

@RestController
public class AsyncController {

    @GetMapping("/async-data")
    public DeferredResult<String> getAsyncData() {
        DeferredResult<String> result = new DeferredResult<>(5000L); // 5 sec timeout

        // Process in background thread
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(2000);  // Simulate long operation
                result.setResult("Async data ready!");
            } catch (Exception e) {
                result.setErrorResult("Failed!");
            }
        });

        // Return immediately (non-blocking)
        return result;
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

1. Request comes in
   ↓
2. DeferredResult created and returned immediately
   ↓
3. Request thread is FREE (can handle other requests)
   ↓
4. Background thread processes
   ↓
5. setResult() called when ready
   ↓
6. Spring sends response to client
   ↓
7. Client gets response after 2 seconds
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… Long-running operations
βœ… External API calls
βœ… Database queries
βœ… High concurrency needed
Enter fullscreen mode Exit fullscreen mode

9️⃣ Callable - ASYNC Simple

How it Works:

@RestController
public class AsyncController {

    @GetMapping("/callable-data")
    public Callable<String> getCallableData() {
        return () -> {
            Thread.sleep(2000);  // Long operation
            return "Data ready!";
        };
    }

    @GetMapping("/users/async")
    public Callable<List<User>> getUsersAsync() {
        return () -> {
            // Long database query
            return userService.findAll();
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

1. Request comes in
   ↓
2. Spring puts Callable in TaskExecutor queue
   ↓
3. Request thread is FREE
   ↓
4. Background thread executes Callable
   ↓
5. Result returned when ready
   ↓
6. Client gets response
Enter fullscreen mode Exit fullscreen mode

Difference: Callable vs DeferredResult:

Callable:
- Spring manages threading automatically
- Simpler code
- Less control

DeferredResult:
- You manage threading
- More control
- Can integrate with external systems
Enter fullscreen mode Exit fullscreen mode

πŸ”Ÿ CompletableFuture - Modern ASYNC

How it Works:

@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/future-data")
    public CompletableFuture<ResponseEntity<String>> getFutureData() {
        return asyncService.processAsync()
            .thenApply(result -> ResponseEntity.ok(result))
            .exceptionally(ex -> ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("Error: " + ex.getMessage())
            );
    }

    @GetMapping("/users/future")
    public CompletableFuture<List<User>> getUsersFuture() {
        return CompletableFuture.supplyAsync(() -> {
            return userService.findAll();
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

// AsyncService
@Service
public class AsyncService {

    @Async
    public CompletableFuture<String> processAsync() {
        return CompletableFuture.supplyAsync(() -> {
            // Long operation
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "Processed!";
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… Modern async programming
βœ… Complex async workflows
βœ… Chaining multiple async operations
βœ… Better error handling
Enter fullscreen mode Exit fullscreen mode

1️⃣1️⃣ Flux / Mono - REACTIVE (WebFlux)

How it Works:

@RestController
public class ReactiveController {

    // Mono - Single value (like Optional)
    @GetMapping("/user/{id}")
    public Mono<User> getUser(@PathVariable Long id) {
        return userReactiveRepository.findById(id);
    }

    // Flux - Stream of values
    @GetMapping(value = "/users/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> streamUsers() {
        return userReactiveRepository.findAll()
            .delayElements(Duration.ofSeconds(1));  // Emit 1 user per second
    }

    // Flux to List
    @GetMapping("/users")
    public Flux<User> getAllUsers() {
        return userReactiveRepository.findAll();
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

Traditional (Blocking):
Request β†’ Wait β†’ Database β†’ Wait β†’ Response
(Thread blocked entire time)

Reactive (Non-blocking):
Request β†’ Register callback β†’ Thread FREE
Database ready β†’ Callback fired β†’ Response
(Thread only used when needed)
Enter fullscreen mode Exit fullscreen mode

Dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… High-throughput applications
βœ… Streaming data
βœ… Real-time updates
βœ… Backpressure handling
❌ Complex learning curve
Enter fullscreen mode Exit fullscreen mode

1️⃣2️⃣ StreamingResponseBody - Large File Streaming

How it Works:

@RestController
public class FileController {

    @GetMapping("/download/large-file")
    public ResponseEntity<StreamingResponseBody> downloadLargeFile() {

        StreamingResponseBody stream = outputStream -> {
            // Read file in chunks and write to output
            try (InputStream inputStream = new FileInputStream("large-file.zip")) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }
        };

        return ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=large-file.zip")
            .body(stream);
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

1. Client requests file
   ↓
2. StreamingResponseBody returned immediately
   ↓
3. File streamed in chunks (not loaded in memory)
   ↓
4. Client receives data as it's streamed
   ↓
5. No OutOfMemory errors for large files
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… Large file downloads (GB files)
βœ… Video streaming
βœ… Database export
βœ… Report generation
Enter fullscreen mode Exit fullscreen mode

1️⃣3️⃣ ResponseBodyEmitter - Stream Multiple Objects

How it Works:

@RestController
public class StreamController {

    @GetMapping("/stream/data")
    public ResponseBodyEmitter streamData() {
        ResponseBodyEmitter emitter = new ResponseBodyEmitter();

        // Send data asynchronously
        CompletableFuture.runAsync(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    emitter.send("Data " + i, MediaType.TEXT_PLAIN);
                    Thread.sleep(1000);  // 1 second delay
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });

        return emitter;
    }

    @GetMapping("/stream/users")
    public ResponseBodyEmitter streamUsers() {
        ResponseBodyEmitter emitter = new ResponseBodyEmitter();

        CompletableFuture.runAsync(() -> {
            try {
                List<User> users = userService.findAll();
                for (User user : users) {
                    emitter.send(user);  // Send one by one
                    Thread.sleep(500);
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });

        return emitter;
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

1. ResponseBodyEmitter returned immediately
   ↓
2. Connection kept open
   ↓
3. Data sent progressively via send()
   ↓
4. Client receives data as it arrives
   ↓
5. complete() closes connection
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… Progress updates
βœ… Live data feeds
βœ… Batch processing updates
Enter fullscreen mode Exit fullscreen mode

1️⃣4️⃣ SseEmitter - Server-Sent Events

How it Works:

@RestController
public class NotificationController {

    private final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();

    // Client subscribes to notifications
    @GetMapping("/notifications/subscribe")
    public SseEmitter subscribe() {
        SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);

        emitters.add(emitter);

        emitter.onCompletion(() -> emitters.remove(emitter));
        emitter.onTimeout(() -> emitters.remove(emitter));

        return emitter;
    }

    // Send notification to all subscribers
    @PostMapping("/notifications/send")
    public void sendNotification(@RequestBody String message) {
        List<SseEmitter> deadEmitters = new ArrayList<>();

        emitters.forEach(emitter -> {
            try {
                emitter.send(SseEmitter.event()
                    .name("notification")
                    .data(message));
            } catch (Exception e) {
                deadEmitters.add(emitter);
            }
        });

        emitters.removeAll(deadEmitters);
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

1. Client subscribes (GET /notifications/subscribe)
   ↓
2. SseEmitter created and returned
   ↓
3. Connection stays open
   ↓
4. Server sends events via send()
   ↓
5. Client receives real-time updates
   ↓
6. No polling needed
Enter fullscreen mode Exit fullscreen mode

Client Side (JavaScript):

const eventSource = new EventSource('/notifications/subscribe');

eventSource.addEventListener('notification', (event) => {
    console.log('Received:', event.data);
});
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… Real-time notifications
βœ… Live sports scores
βœ… Stock price updates
βœ… Chat applications
βœ… Activity feeds
Enter fullscreen mode Exit fullscreen mode

1️⃣5️⃣ Resource - File Download

How it Works:

@RestController
public class FileDownloadController {

    @GetMapping("/download/file")
    public ResponseEntity<Resource> downloadFile() {
        File file = new File("report.pdf");
        Resource resource = new FileSystemResource(file);

        return ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_PDF)
            .header(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment; filename=\"" + file.getName() + "\"")
            .body(resource);
    }

    @GetMapping("/download/classpath")
    public ResponseEntity<Resource> downloadClasspathFile() {
        Resource resource = new ClassPathResource("static/template.xlsx");

        return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType(
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
            ))
            .header(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment; filename=\"template.xlsx\"")
            .body(resource);
    }
}
Enter fullscreen mode Exit fullscreen mode

Resource Types:

// FileSystemResource - file from file system
Resource resource = new FileSystemResource("/path/to/file.pdf");

// ClassPathResource - file from classpath (src/main/resources)
Resource resource = new ClassPathResource("static/file.pdf");

// UrlResource - file from URL
Resource resource = new UrlResource("http://example.com/file.pdf");

// ByteArrayResource - from byte array
Resource resource = new ByteArrayResource(fileBytes);

// InputStreamResource - from InputStream
Resource resource = new InputStreamResource(inputStream);
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… File downloads
βœ… Report generation
βœ… Template downloads
βœ… Export functionality
Enter fullscreen mode Exit fullscreen mode

1️⃣6️⃣ byte[] - Binary Data

How it Works:

@RestController
public class ImageController {

    @GetMapping("/image/{id}")
    public ResponseEntity<byte[]> getImage(@PathVariable Long id) {
        byte[] imageBytes = imageService.getImageBytes(id);

        return ResponseEntity.ok()
            .contentType(MediaType.IMAGE_JPEG)
            .body(imageBytes);
    }

    @GetMapping("/pdf/{id}")
    public ResponseEntity<byte[]> getPdf(@PathVariable Long id) {
        byte[] pdfBytes = pdfService.generatePdf(id);

        return ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_PDF)
            .header(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment; filename=\"report.pdf\"")
            .body(pdfBytes);
    }
}
Enter fullscreen mode Exit fullscreen mode

Behind the Scenes:

1. byte[] returned
   ↓
2. Spring writes bytes directly to response
   ↓
3. No conversion needed
   ↓
4. Client receives binary data
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… Images
βœ… PDFs
βœ… Excel files
βœ… Any binary data
⚠️  WARNING: Load entire file in memory (use streaming for large files)
Enter fullscreen mode Exit fullscreen mode

1️⃣7️⃣ HttpHeaders - Just Headers

How it Works:

@RestController
public class HeaderController {

    @RequestMapping(value = "/options", method = RequestMethod.OPTIONS)
    public ResponseEntity<Void> options() {
        HttpHeaders headers = new HttpHeaders();
        headers.setAllow(Set.of(
            HttpMethod.GET,
            HttpMethod.POST,
            HttpMethod.PUT,
            HttpMethod.DELETE,
            HttpMethod.OPTIONS
        ));

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

Use Case:

βœ… OPTIONS requests
βœ… CORS preflight
βœ… Header-only responses
Enter fullscreen mode Exit fullscreen mode

1️⃣8️⃣ Map - Dynamic Response

How it Works:

@RestController
public class DynamicController {

    @GetMapping("/stats")
    public Map<String, Object> getStats() {
        Map<String, Object> stats = new HashMap<>();
        stats.put("totalUsers", userService.count());
        stats.put("activeUsers", userService.countActive());
        stats.put("timestamp", System.currentTimeMillis());
        return stats;
    }

    @GetMapping("/data")
    public ResponseEntity<Map<String, Object>> getData() {
        Map<String, Object> data = new HashMap<>();
        data.put("success", true);
        data.put("data", userService.findAll());
        data.put("count", userService.count());

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

Response:

{
  "totalUsers": 1000,
  "activeUsers": 750,
  "timestamp": 1709388000000
}
Enter fullscreen mode Exit fullscreen mode

Use Case:

βœ… Quick prototyping
βœ… Dynamic fields
❌ Not type-safe
❌ Not recommended for production
Enter fullscreen mode Exit fullscreen mode

πŸ“Š COMPARISON TABLE

Return Type Status Control Headers Control Async Use Case Complexity
Object ❌ ❌ ❌ Simple APIs ⭐
ResponseEntity βœ… βœ… ❌ Most APIs ⭐⭐
HttpEntity ❌ βœ… ❌ Rare ⭐⭐
String ❌ ❌ ❌ MVC Views ⭐
void ⚠️ ❌ ❌ Fire-forget ⭐
ModelAndView ❌ ❌ ❌ MVC ⭐⭐
DeferredResult βœ… βœ… βœ… Async ⭐⭐⭐
Callable ❌ ❌ βœ… Simple Async ⭐⭐
CompletableFuture βœ… βœ… βœ… Modern Async ⭐⭐⭐
Flux/Mono βœ… βœ… βœ… Reactive ⭐⭐⭐⭐
StreamingResponseBody βœ… βœ… βœ… Large files ⭐⭐⭐
ResponseBodyEmitter βœ… βœ… βœ… Streaming ⭐⭐⭐
SseEmitter βœ… βœ… βœ… Real-time ⭐⭐⭐
Resource βœ… βœ… ❌ File download ⭐⭐
byte[] βœ… βœ… ❌ Binary data ⭐⭐

🎯 DECISION TREE - Which to Use?

Need to return data?
β”œβ”€ Simple JSON response?
β”‚  β”œβ”€ Need status/headers control? β†’ ResponseEntity<T>
β”‚  └─ Don't care about status? β†’ Direct Object
β”‚
β”œβ”€ File download?
β”‚  β”œβ”€ Small file? β†’ Resource or byte[]
β”‚  └─ Large file? β†’ StreamingResponseBody
β”‚
β”œβ”€ Async processing?
β”‚  β”œβ”€ Simple async? β†’ Callable<T>
β”‚  β”œβ”€ Complex async? β†’ CompletableFuture<T>
β”‚  └─ Full control? β†’ DeferredResult<T>
β”‚
β”œβ”€ Real-time updates?
β”‚  β”œβ”€ Server-sent events? β†’ SseEmitter
β”‚  β”œβ”€ Streaming objects? β†’ ResponseBodyEmitter
β”‚  └─ Reactive? β†’ Flux<T> / Mono<T>
β”‚
β”œβ”€ HTML page?
β”‚  β”œβ”€ With data? β†’ ModelAndView
β”‚  └─ Just redirect? β†’ String
β”‚
└─ No response needed?
   └─ void with @ResponseStatus
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ BEST PRACTICES

1. For REST APIs (90% cases):

// βœ… RECOMMENDED
@RestController
public class UserController {

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

2. For Simple Cases:

// βœ… OK for simple endpoints
@RestController
public class HealthController {

    @GetMapping("/health")
    public String health() {
        return "OK";
    }
}
Enter fullscreen mode Exit fullscreen mode

3. For File Downloads:

// βœ… RECOMMENDED
@GetMapping("/download")
public ResponseEntity<Resource> download() {
    Resource resource = new FileSystemResource(file);
    return ResponseEntity.ok()
        .contentType(MediaType.APPLICATION_PDF)
        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=file.pdf")
        .body(resource);
}
Enter fullscreen mode Exit fullscreen mode

4. For Async Operations:

// βœ… RECOMMENDED (Modern)
@GetMapping("/async")
public CompletableFuture<ResponseEntity<Data>> getAsync() {
    return service.processAsync()
        .thenApply(ResponseEntity::ok)
        .exceptionally(ex -> ResponseEntity.status(500).build());
}
Enter fullscreen mode Exit fullscreen mode

🚫 WHAT NOT TO DO

// ❌ DON'T: Mix concerns
@GetMapping("/users")
public Object getUsers() {  // Bad: Object type
    return userService.findAll();
}

// ❌ DON'T: Return null
@GetMapping("/user")
public User getUser() {
    return null;  // Bad: NullPointerException
}

// ❌ DON'T: Ignore errors
@GetMapping("/data")
public Data getData() {
    return service.getData();  // Bad: What if exception?
}

// βœ… DO: Handle properly
@GetMapping("/data")
public ResponseEntity<Data> getData() {
    try {
        return ResponseEntity.ok(service.getData());
    } catch (Exception e) {
        return ResponseEntity.status(500).build();
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“š SUMMARY - Quick Reference

// πŸ”₯ MOST USED (80% of cases)
ResponseEntity<T>              // Full control REST API
Object (User, List<User>)      // Simple REST API

// 🎯 SPECIFIC USE CASES
Resource / byte[]              // File downloads
CompletableFuture<T>           // Async operations
SseEmitter                     // Real-time updates
StreamingResponseBody          // Large file streaming

// 🌐 WEB PAGES (Not REST)
String                         // View name
ModelAndView                   // MVC with data

// ⚑ REACTIVE
Flux<T> / Mono<T>             // WebFlux reactive

// ❌ RARELY USED
HttpEntity<T>                  // Use ResponseEntity instead
void                           // Use ResponseEntity<Void>
Map<String, Object>            // Not type-safe
Enter fullscreen mode Exit fullscreen mode

Yeh complete guide hai - save kar lo! Interview aur production code dono mein kaam aayega! πŸš€

Top comments (0)