π 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> β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
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";
}
}
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
Response:
// User object automatically converted to JSON
{
"id": 123,
"name": "Raj",
"email": "raj@email.com"
}
Limitations:
β Cannot control status code (always 200)
β Cannot add custom headers
β Cannot handle errors properly
β No control over response format
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
}
}
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
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
}
Advantages:
β
Full control over status code
β
Can add custom headers
β
Can handle different scenarios (success/error)
β
Type-safe with generics
β
Industry standard
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)
}
}
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;
}
}
Use Case:
β
When you need headers but don't care about status
β Rarely used (ResponseEntity is better)
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";
}
}
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
Use Case:
β
Traditional web applications (HTML pages)
β
Server-side rendering
β NOT for REST APIs
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);
}
}
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
Use Case:
β
Fire-and-forget operations
β
Logging, auditing
β Client doesn't know if operation succeeded
β Cannot return data
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);
}
}
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);
}
}
Use Case:
β
Traditional Spring MVC
β
Server-side rendering
β NOT for REST APIs
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
}
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
Note:
@RestController = @Controller + @ResponseBody (on every method)
So these are same:
1. @RestController with no @ResponseBody
2. @Controller with @ResponseBody on each method
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;
}
}
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
Use Case:
β
Long-running operations
β
External API calls
β
Database queries
β
High concurrency needed
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();
};
}
}
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
Difference: Callable vs DeferredResult:
Callable:
- Spring manages threading automatically
- Simpler code
- Less control
DeferredResult:
- You manage threading
- More control
- Can integrate with external systems
π 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();
});
}
}
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!";
});
}
}
Use Case:
β
Modern async programming
β
Complex async workflows
β
Chaining multiple async operations
β
Better error handling
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();
}
}
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)
Dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Use Case:
β
High-throughput applications
β
Streaming data
β
Real-time updates
β
Backpressure handling
β Complex learning curve
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);
}
}
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
Use Case:
β
Large file downloads (GB files)
β
Video streaming
β
Database export
β
Report generation
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;
}
}
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
Use Case:
β
Progress updates
β
Live data feeds
β
Batch processing updates
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);
}
}
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
Client Side (JavaScript):
const eventSource = new EventSource('/notifications/subscribe');
eventSource.addEventListener('notification', (event) => {
console.log('Received:', event.data);
});
Use Case:
β
Real-time notifications
β
Live sports scores
β
Stock price updates
β
Chat applications
β
Activity feeds
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);
}
}
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);
Use Case:
β
File downloads
β
Report generation
β
Template downloads
β
Export functionality
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);
}
}
Behind the Scenes:
1. byte[] returned
β
2. Spring writes bytes directly to response
β
3. No conversion needed
β
4. Client receives binary data
Use Case:
β
Images
β
PDFs
β
Excel files
β
Any binary data
β οΈ WARNING: Load entire file in memory (use streaming for large files)
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();
}
}
Use Case:
β
OPTIONS requests
β
CORS preflight
β
Header-only responses
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);
}
}
Response:
{
"totalUsers": 1000,
"activeUsers": 750,
"timestamp": 1709388000000
}
Use Case:
β
Quick prototyping
β
Dynamic fields
β Not type-safe
β Not recommended for production
π 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
π‘ 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));
}
}
2. For Simple Cases:
// β
OK for simple endpoints
@RestController
public class HealthController {
@GetMapping("/health")
public String health() {
return "OK";
}
}
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);
}
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());
}
π« 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();
}
}
π 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
Yeh complete guide hai - save kar lo! Interview aur production code dono mein kaam aayega! π
Top comments (0)