Spring Boot Controller - Kitne Tarah Ka Code Likh Sakte Ho 🚀
Controller Mein 12 Major Patterns/Types
┌─────────────────────────────────────────────────────┐
│ Spring Boot Controller - All Possible Patterns │
├─────────────────────────────────────────────────────┤
│ │
│ 1️⃣ Basic CRUD Operations │
│ 2️⃣ Pagination & Sorting │
│ 3️⃣ Search & Filtering │
│ 4️⃣ File Upload/Download │
│ 5️⃣ Batch Operations │
│ 6️⃣ Authentication & Authorization │
│ 7️⃣ Exception Handling │
│ 8️⃣ Async Operations │
│ 9️⃣ Caching │
│ 🔟 Validation (Complex) │
│ 1️⃣1️⃣ Webhooks & Callbacks │
│ 1️⃣2️⃣ Export Data (CSV, Excel, PDF) │
│ │
└─────────────────────────────────────────────────────┘
1️⃣ Basic CRUD Operations
@RestController
@RequestMapping("/api/v1/products")
@RequiredArgsConstructor
@Validated
public class ProductController {
private final ProductService productService;
private final ProductMapper productMapper;
/**
* CREATE - New product
*/
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<ApiResponse<ProductResponse>> createProduct(
@Valid @RequestBody ProductRequest request
) {
Product product = productService.create(request);
ProductResponse response = productMapper.toResponse(product);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse.success("Product created", response));
}
/**
* READ - Get all products
*/
@GetMapping
public ResponseEntity<ApiResponse<List<ProductResponse>>> getAllProducts() {
List<Product> products = productService.findAll();
List<ProductResponse> responses = productMapper.toResponseList(products);
return ResponseEntity.ok(ApiResponse.success(responses));
}
/**
* READ - Get single product by ID
*/
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<ProductResponse>> getProductById(
@PathVariable @Min(1) Long id
) {
Product product = productService.findById(id);
ProductResponse response = productMapper.toResponse(product);
return ResponseEntity.ok(ApiResponse.success(response));
}
/**
* UPDATE - Full update
*/
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<ProductResponse>> updateProduct(
@PathVariable Long id,
@Valid @RequestBody ProductRequest request
) {
Product updated = productService.update(id, request);
ProductResponse response = productMapper.toResponse(updated);
return ResponseEntity.ok(
ApiResponse.success("Product updated", response)
);
}
/**
* PARTIAL UPDATE - Update specific fields
*/
@PatchMapping("/{id}")
public ResponseEntity<ApiResponse<ProductResponse>> partialUpdate(
@PathVariable Long id,
@RequestBody Map<String, Object> updates
) {
Product updated = productService.partialUpdate(id, updates);
ProductResponse response = productMapper.toResponse(updated);
return ResponseEntity.ok(ApiResponse.success(response));
}
/**
* DELETE - Soft delete
*/
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.delete(id);
return ResponseEntity.noContent().build();
}
/**
* DELETE - Hard delete (Admin only)
*/
@DeleteMapping("/{id}/hard")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Void> hardDeleteProduct(@PathVariable Long id) {
productService.hardDelete(id);
return ResponseEntity.noContent().build();
}
}
2️⃣ Pagination & Sorting
@RestController
@RequestMapping("/api/v1/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
/**
* Get products with pagination
*/
@GetMapping("/paginated")
public ResponseEntity<ApiResponse<PageResponse<ProductResponse>>> getProductsPaginated(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "id") String sortBy,
@RequestParam(defaultValue = "ASC") String sortDirection
) {
// Validate pagination parameters
if (page < 0 || size <= 0 || size > 100) {
throw new InvalidRequestException("Invalid pagination parameters");
}
// Create pageable
Sort.Direction direction = Sort.Direction.fromString(sortDirection);
Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sortBy));
// Get paginated data
Page<Product> productPage = productService.findAll(pageable);
// Convert to response
List<ProductResponse> content = productPage.getContent()
.stream()
.map(productMapper::toResponse)
.collect(Collectors.toList());
PageResponse<ProductResponse> pageResponse = PageResponse.<ProductResponse>builder()
.content(content)
.pageNumber(productPage.getNumber())
.pageSize(productPage.getSize())
.totalElements(productPage.getTotalElements())
.totalPages(productPage.getTotalPages())
.first(productPage.isFirst())
.last(productPage.isLast())
.empty(productPage.isEmpty())
.build();
return ResponseEntity.ok(ApiResponse.success(pageResponse));
}
/**
* Multiple sort fields
*/
@GetMapping("/multi-sort")
public ResponseEntity<ApiResponse<List<ProductResponse>>> getProductsMultiSort(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) List<String> sort
) {
// Example: ?sort=price,asc&sort=name,desc
List<Sort.Order> orders = new ArrayList<>();
if (sort != null) {
for (String sortParam : sort) {
String[] parts = sortParam.split(",");
String field = parts[0];
Sort.Direction direction = parts.length > 1
? Sort.Direction.fromString(parts[1])
: Sort.Direction.ASC;
orders.add(new Sort.Order(direction, field));
}
}
Pageable pageable = PageRequest.of(page, size, Sort.by(orders));
Page<Product> productPage = productService.findAll(pageable);
List<ProductResponse> responses = productMapper.toResponseList(
productPage.getContent()
);
return ResponseEntity.ok(ApiResponse.success(responses));
}
}
3️⃣ Search & Filtering (Advanced)
@RestController
@RequestMapping("/api/v1/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
/**
* Simple search by name
*/
@GetMapping("/search")
public ResponseEntity<ApiResponse<List<ProductResponse>>> searchProducts(
@RequestParam String query
) {
List<Product> products = productService.searchByName(query);
List<ProductResponse> responses = productMapper.toResponseList(products);
return ResponseEntity.ok(ApiResponse.success(responses));
}
/**
* Advanced filtering with multiple criteria
*/
@GetMapping("/filter")
public ResponseEntity<ApiResponse<PageResponse<ProductResponse>>> filterProducts(
@RequestParam(required = false) String name,
@RequestParam(required = false) String category,
@RequestParam(required = false) BigDecimal minPrice,
@RequestParam(required = false) BigDecimal maxPrice,
@RequestParam(required = false) Boolean inStock,
@RequestParam(required = false) String brand,
@RequestParam(required = false) List<String> tags,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size
) {
// Build filter object
ProductFilter filter = ProductFilter.builder()
.name(name)
.category(category)
.minPrice(minPrice)
.maxPrice(maxPrice)
.inStock(inStock)
.brand(brand)
.tags(tags)
.build();
Pageable pageable = PageRequest.of(page, size);
// Use Specification pattern for dynamic queries
Page<Product> productPage = productService.findByFilter(filter, pageable);
// Map to response
PageResponse<ProductResponse> pageResponse = buildPageResponse(productPage);
return ResponseEntity.ok(ApiResponse.success(pageResponse));
}
/**
* Full-text search (if using Elasticsearch)
*/
@GetMapping("/full-text-search")
public ResponseEntity<ApiResponse<List<ProductResponse>>> fullTextSearch(
@RequestParam String query,
@RequestParam(defaultValue = "10") int limit
) {
List<Product> products = productService.fullTextSearch(query, limit);
List<ProductResponse> responses = productMapper.toResponseList(products);
return ResponseEntity.ok(ApiResponse.success(responses));
}
/**
* Search with autocomplete
*/
@GetMapping("/autocomplete")
public ResponseEntity<ApiResponse<List<String>>> autocomplete(
@RequestParam String query,
@RequestParam(defaultValue = "10") int limit
) {
List<String> suggestions = productService.getAutocompleteSuggestions(
query,
limit
);
return ResponseEntity.ok(ApiResponse.success(suggestions));
}
}
4️⃣ File Upload/Download
@RestController
@RequestMapping("/api/v1/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
private final FileStorageService fileStorageService;
/**
* Upload single file (product image)
*/
@PostMapping("/{id}/image")
public ResponseEntity<ApiResponse<String>> uploadProductImage(
@PathVariable Long id,
@RequestParam("file") MultipartFile file
) {
// Validate file
if (file.isEmpty()) {
throw new InvalidRequestException("File is empty");
}
// Validate file type
String contentType = file.getContentType();
if (contentType == null || !contentType.startsWith("image/")) {
throw new InvalidRequestException("Only image files are allowed");
}
// Validate file size (max 5MB)
if (file.getSize() > 5 * 1024 * 1024) {
throw new InvalidRequestException("File size exceeds 5MB limit");
}
// Save file
String imageUrl = fileStorageService.storeFile(file, "products");
// Update product
productService.updateImage(id, imageUrl);
return ResponseEntity.ok(
ApiResponse.success("Image uploaded successfully", imageUrl)
);
}
/**
* Upload multiple files
*/
@PostMapping("/{id}/images")
public ResponseEntity<ApiResponse<List<String>>> uploadMultipleImages(
@PathVariable Long id,
@RequestParam("files") List<MultipartFile> files
) {
if (files.size() > 5) {
throw new InvalidRequestException("Maximum 5 images allowed");
}
List<String> imageUrls = new ArrayList<>();
for (MultipartFile file : files) {
String url = fileStorageService.storeFile(file, "products");
imageUrls.add(url);
}
productService.updateImages(id, imageUrls);
return ResponseEntity.ok(
ApiResponse.success("Images uploaded successfully", imageUrls)
);
}
/**
* Download file (product brochure)
*/
@GetMapping("/{id}/brochure")
public ResponseEntity<Resource> downloadBrochure(@PathVariable Long id) {
Product product = productService.findById(id);
if (product.getBrochureUrl() == null) {
throw new ResourceNotFoundException("Brochure not found");
}
Resource resource = fileStorageService.loadFileAsResource(
product.getBrochureUrl()
);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
/**
* Stream file (for images)
*/
@GetMapping("/{id}/image")
public ResponseEntity<byte[]> getProductImage(@PathVariable Long id) {
Product product = productService.findById(id);
byte[] imageBytes = fileStorageService.loadFileAsBytes(
product.getImageUrl()
);
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_JPEG)
.body(imageBytes);
}
/**
* Bulk import from CSV
*/
@PostMapping("/import")
public ResponseEntity<ApiResponse<ImportResult>> importProductsFromCSV(
@RequestParam("file") MultipartFile file
) {
if (!file.getOriginalFilename().endsWith(".csv")) {
throw new InvalidRequestException("Only CSV files are allowed");
}
ImportResult result = productService.importFromCSV(file);
return ResponseEntity.ok(
ApiResponse.success("Import completed", result)
);
}
}
5️⃣ Batch Operations
@RestController
@RequestMapping("/api/v1/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
/**
* Bulk create products
*/
@PostMapping("/bulk")
public ResponseEntity<ApiResponse<BulkOperationResult>> bulkCreateProducts(
@Valid @RequestBody List<ProductRequest> requests
) {
if (requests.size() > 100) {
throw new InvalidRequestException(
"Maximum 100 products can be created at once"
);
}
BulkOperationResult result = productService.bulkCreate(requests);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse.success("Bulk creation completed", result));
}
/**
* Bulk update products
*/
@PutMapping("/bulk")
public ResponseEntity<ApiResponse<BulkOperationResult>> bulkUpdateProducts(
@Valid @RequestBody BulkUpdateRequest request
) {
BulkOperationResult result = productService.bulkUpdate(
request.getProductIds(),
request.getUpdates()
);
return ResponseEntity.ok(
ApiResponse.success("Bulk update completed", result)
);
}
/**
* Bulk delete products
*/
@DeleteMapping("/bulk")
public ResponseEntity<ApiResponse<BulkOperationResult>> bulkDeleteProducts(
@RequestBody List<Long> productIds
) {
if (productIds.size() > 50) {
throw new InvalidRequestException(
"Maximum 50 products can be deleted at once"
);
}
BulkOperationResult result = productService.bulkDelete(productIds);
return ResponseEntity.ok(
ApiResponse.success("Bulk deletion completed", result)
);
}
/**
* Bulk price update
*/
@PatchMapping("/bulk/price")
public ResponseEntity<ApiResponse<Integer>> bulkUpdatePrice(
@RequestParam String category,
@RequestParam BigDecimal percentage,
@RequestParam String operation // "INCREASE" or "DECREASE"
) {
int updatedCount = productService.bulkUpdatePrice(
category,
percentage,
operation
);
return ResponseEntity.ok(
ApiResponse.success(
updatedCount + " products updated",
updatedCount
)
);
}
}
// DTOs for bulk operations
@Data
@Builder
class BulkOperationResult {
private Integer totalRequested;
private Integer successCount;
private Integer failureCount;
private List<String> errors;
private List<Long> successfulIds;
private List<Long> failedIds;
}
@Data
class BulkUpdateRequest {
@NotEmpty
private List<Long> productIds;
@NotNull
private Map<String, Object> updates;
}
6️⃣ Authentication & Authorization
@RestController
@RequestMapping("/api/v1/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
/**
* Public endpoint - no authentication required
*/
@GetMapping("/public")
public ResponseEntity<ApiResponse<List<ProductResponse>>> getPublicProducts() {
List<Product> products = productService.findAllPublic();
return ResponseEntity.ok(ApiResponse.success(
productMapper.toResponseList(products)
));
}
/**
* Requires authentication (any logged-in user)
*/
@GetMapping("/private")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<ApiResponse<List<ProductResponse>>> getPrivateProducts(
@AuthenticationPrincipal UserDetails userDetails
) {
String username = userDetails.getUsername();
log.info("User {} accessing private products", username);
List<Product> products = productService.findAll();
return ResponseEntity.ok(ApiResponse.success(
productMapper.toResponseList(products)
));
}
/**
* Requires ADMIN role
*/
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<ProductResponse>> createProduct(
@Valid @RequestBody ProductRequest request,
@AuthenticationPrincipal UserDetails userDetails
) {
Product product = productService.create(request);
log.info("Product created by admin: {}", userDetails.getUsername());
return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse.success(productMapper.toResponse(product)));
}
/**
* Requires ADMIN or MANAGER role
*/
@PutMapping("/{id}")
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public ResponseEntity<ApiResponse<ProductResponse>> updateProduct(
@PathVariable Long id,
@Valid @RequestBody ProductRequest request
) {
Product updated = productService.update(id, request);
return ResponseEntity.ok(
ApiResponse.success(productMapper.toResponse(updated))
);
}
/**
* Custom authorization logic
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or @productSecurity.canDelete(#id)")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.delete(id);
return ResponseEntity.noContent().build();
}
/**
* Get current user's products
*/
@GetMapping("/my-products")
public ResponseEntity<ApiResponse<List<ProductResponse>>> getMyProducts(
@RequestHeader("X-User-Id") Long userId
) {
List<Product> products = productService.findByUserId(userId);
return ResponseEntity.ok(
ApiResponse.success(productMapper.toResponseList(products))
);
}
/**
* Check permission before accessing
*/
@GetMapping("/{id}/details")
public ResponseEntity<ApiResponse<ProductResponse>> getProductDetails(
@PathVariable Long id,
@RequestHeader("X-User-Id") Long userId
) {
// Check if user has permission
if (!productService.hasAccess(id, userId)) {
throw new AccessDeniedException(
"You don't have permission to view this product"
);
}
Product product = productService.findById(id);
return ResponseEntity.ok(
ApiResponse.success(productMapper.toResponse(product))
);
}
}
// Custom security bean
@Component
public class ProductSecurity {
@Autowired
private ProductService productService;
public boolean canDelete(Long productId) {
// Custom logic to check if current user can delete
Product product = productService.findById(productId);
// Check ownership, status, etc.
return product.getStatus() != ProductStatus.PUBLISHED;
}
}
7️⃣ Async Operations
@RestController
@RequestMapping("/api/v1/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
private final AsyncTaskService asyncTaskService;
/**
* Async processing - return immediately with task ID
*/
@PostMapping("/bulk-process")
public ResponseEntity<ApiResponse<String>> bulkProcessProducts(
@RequestBody BulkProcessRequest request
) {
// Start async task
String taskId = asyncTaskService.startBulkProcessing(request);
return ResponseEntity
.status(HttpStatus.ACCEPTED)
.body(ApiResponse.success(
"Processing started. Track progress with task ID",
taskId
));
}
/**
* Check async task status
*/
@GetMapping("/tasks/{taskId}")
public ResponseEntity<ApiResponse<TaskStatus>> getTaskStatus(
@PathVariable String taskId
) {
TaskStatus status = asyncTaskService.getTaskStatus(taskId);
return ResponseEntity.ok(ApiResponse.success(status));
}
/**
* CompletableFuture example
*/
@GetMapping("/{id}/analytics")
public CompletableFuture<ResponseEntity<ApiResponse<ProductAnalytics>>>
getProductAnalytics(@PathVariable Long id) {
return productService.getAnalyticsAsync(id)
.thenApply(analytics -> ResponseEntity.ok(
ApiResponse.success(analytics)
))
.exceptionally(ex -> ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error(ex.getMessage()))
);
}
/**
* Send notification asynchronously
*/
@PostMapping("/{id}/notify")
public ResponseEntity<ApiResponse<Void>> notifyProductUpdate(
@PathVariable Long id,
@RequestBody NotificationRequest request
) {
// Fire and forget
asyncTaskService.sendNotificationAsync(id, request);
return ResponseEntity.ok(
ApiResponse.success("Notification queued successfully", null)
);
}
}
@Data
@Builder
class TaskStatus {
private String taskId;
private String status; // PENDING, PROCESSING, COMPLETED, FAILED
private Integer progress; // 0-100
private String message;
private LocalDateTime startedAt;
private LocalDateTime completedAt;
private Object result;
}
Continuing with remaining 5 patterns in next part...
Would you like me to continue with:
- 8️⃣ Caching
- 9️⃣ Complex Validation
- 🔟 Webhooks
- 1️⃣1️⃣ Export (CSV, Excel, PDF)
- 1️⃣2️⃣ Real-time/WebSocket
Top comments (0)