When learning Spring Boot, one of the most confusing things is seeing controllers written in many different ways.
Sometimes a method returns a DTO.
Sometimes ResponseEntity<T>.
Sometimes void, String, ModelAndView, CompletableFuture, Mono, and more.
At first it feels random.
But each return type has a specific purpose.
This article is a practical cheat sheet to quickly understand the most common controller patterns in Spring Boot REST APIs.
What Is a Controller?
A controller handles HTTP requests.
Its job is simple:
- Receive the request
- Call the service layer
- Return a response
Example:
@RestController
@RequestMapping("/api/users")
public class UserController {
}
Most Common HTTP Annotations
| Annotation | HTTP Method | Purpose |
|---|---|---|
@GetMapping |
GET | Retrieve data |
@PostMapping |
POST | Create data |
@PutMapping |
PUT | Replace/update completely |
@PatchMapping |
PATCH | Partial update |
@DeleteMapping |
DELETE | Delete data |
1. Returning an Object Directly
Example
@GetMapping
public List<UserDTO> getAllUsers() {
return userService.getAllUsers();
}
What Spring Does Automatically
- Converts the object to JSON
- Returns
200 OK
Best For
- Simple CRUD APIs
- Clean code
- Fast development
Advantages
| Advantage | Why |
|---|---|
| Less code | No boilerplate |
| Easy to read | Simple methods |
| Very common | Standard REST pattern |
2. Returning ResponseEntity<T>
Example
@GetMapping
public ResponseEntity<List<UserDTO>> getAllUsers() {
return ResponseEntity.ok(userService.getAllUsers());
}
Why Use It?
ResponseEntity gives full control over the HTTP response.
You can customize:
- Status code
- Headers
- Body
Custom Example
return ResponseEntity
.status(HttpStatus.CREATED)
.body(user);
Best For
- Professional APIs
- Authentication endpoints
- Pagination
- Custom status codes
Advantages
| Advantage | Why |
|---|---|
| Full HTTP control | Status + headers |
| Flexible | Many response options |
| Explicit | Easy to understand behavior |
3. Returning void
Example
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
What Happens?
- No response body
- Returns HTTP
204 No Content
Best For
- DELETE endpoints
- Simple PATCH operations
Advantages
| Advantage | Why |
|---|---|
| Clean intent | No unnecessary response |
| Lightweight | Smaller HTTP response |
4. Returning DTOs
Example
@PostMapping
public UserResponseDTO createUser(
@RequestBody UserRequestDTO dto
) {
return userService.create(dto);
}
Why Use DTOs?
DTOs help separate:
- API layer
- Database entities
Benefits
| Benefit | Why |
|---|---|
| Security | Avoid exposing internal fields |
| Clean architecture | Better separation |
| Maintainability | Easier future changes |
| API control | Customize responses |
5. Returning String
Example
@GetMapping("/ping")
public String ping() {
return "OK";
}
What It Returns
Plain text.
Best For
- Health checks
- Quick tests
- Debugging
6. Returning HTML Views (ModelAndView)
Example
@GetMapping("/home")
public ModelAndView home() {
return new ModelAndView("home");
}
What Is This?
Traditional Spring MVC.
Instead of JSON, it returns an HTML page.
Usually used with:
- Thymeleaf
- JSP
Best For
- Server-rendered applications
- Traditional MVC projects
Important
Less common in modern REST APIs.
Most modern backends return JSON instead.
7. Asynchronous Controllers (CompletableFuture)
Example
@GetMapping
public CompletableFuture<List<UserDTO>> getUsers() {
return userService.getAsyncUsers();
}
What Happens?
The request is processed asynchronously.
The server thread is not blocked while waiting.
Best For
- Slow external APIs
- High traffic systems
- Async operations
8. Reactive Controllers (Mono / Flux)
Example
@GetMapping
public Mono<UserDTO> getUser() {
return userService.getUser();
}
What Is This?
Reactive programming using Spring WebFlux.
Designed for non-blocking applications.
Reactive Types
| Type | Meaning |
|---|---|
Mono<T> |
0 or 1 result |
Flux<T> |
Multiple results |
Best For
- Streaming
- Real-time systems
- Very high concurrency
Important
Requires Spring WebFlux.
Not the same as traditional Spring MVC.
Understanding Method Visibility
Sometimes you may see something like this:
private void processIncomingMessages(Map<String, Object> value)
This is NOT an endpoint.
Why?
Because:
| Keyword | Meaning |
|---|---|
private |
Internal method only |
void |
Returns nothing |
And it has no:
@GetMapping@PostMapping@PutMapping- etc.
Usually these are helper methods used internally by the controller.
Most Common Patterns in Real REST APIs
GET
@GetMapping
public List<UserDTO> getAll()
POST
@PostMapping
public ResponseEntity<UserDTO> create()
DELETE
@DeleteMapping("/{id}")
public void delete()
Quick Cheat Sheet
| Situation | Recommended Return Type |
|---|---|
| Simple CRUD | DTO/Object |
| Need status/header control | ResponseEntity<T> |
| DELETE endpoint | void |
| HTML pages | ModelAndView |
| Async processing | CompletableFuture |
| Reactive systems |
Mono / Flux
|
Final Thoughts
One of the strange things about learning Spring Boot is realizing there are multiple valid ways to build controllers.
At first that feels confusing.
Later, it becomes flexibility.
In real-world REST APIs, the most common patterns are:
- DTOs
ResponseEntity-
voidfor DELETE operations
The rest are specialized tools for specific situations.
Once you understand why each one exists, Spring controllers become much easier to read and design.
Top comments (0)