DEV Community

Er. Bhupendra
Er. Bhupendra

Posted on

PART 3 :CONTROLLER ALL CONCEPT IN SPRINGBOOT PROJECT

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)              │
│                                                     │
└─────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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));
    }
}
Enter fullscreen mode Exit fullscreen mode

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));
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)