This content originally appeared on DEV Community and was authored by DevCorner
Here’s a Spring Boot RESTful API Controller implementing all the best practices we discussed.
Project Overview
We will create a User Management API with:
Versioning (
v1/users
)
Error Handling (
@RestControllerAdvice
)
Pagination, Sorting, Filtering
Security (JWT Authentication placeholder)
Rate Limiting
Caching (Redis)
Logging
Swagger API Documentation
1. Setup Project Dependencies (Maven)
Add the following dependencies in pom.xml
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Spring Boot Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT for Authentication (Placeholder) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.11.2</version>
</dependency>
<!-- Spring Boot Actuator (Monitoring) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Redis Caching -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- SpringDoc OpenAPI for Swagger -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.0</version>
</dependency>
<!-- Lombok for reducing boilerplate -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
2. Define the User Entity
package com.example.api.model;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String email;
private boolean active;
private String role;
private LocalDateTime createdAt;
}
3. Define the Repository
package com.example.api.repository;
import com.example.api.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Page<User> findByRole(String role, Pageable pageable);
}
4. Implement the Service Layer
package com.example.api.service;
import com.example.api.model.User;
import com.example.api.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Cacheable(value = "users", key = "#id")
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
public Page<User> getUsers(Pageable pageable, String role) {
return role == null ? userRepository.findAll(pageable) : userRepository.findByRole(role, pageable);
}
public User createUser(User user) {
return userRepository.save(user);
}
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
5. Implement Global Exception Handling
package com.example.api.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.LocalDateTime;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException ex) {
ErrorResponse error = new ErrorResponse(LocalDateTime.now(), HttpStatus.BAD_REQUEST.value(), ex.getMessage());
return ResponseEntity.badRequest().body(error);
}
}
6. Implement the User Controller
package com.example.api.controller;
import com.example.api.model.User;
import com.example.api.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@Operation(summary = "Get user by ID")
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
logger.info("Fetching user with ID: {}", id);
Optional<User> user = userService.getUserById(id);
return user.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
}
@Operation(summary = "Get paginated users with optional role filter")
@GetMapping
public ResponseEntity<Page<User>> getUsers(Pageable pageable, @RequestParam(required = false) String role) {
return ResponseEntity.ok(userService.getUsers(pageable, role));
}
@Operation(summary = "Create a new user")
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
return ResponseEntity.status(HttpStatus.CREATED).body(userService.createUser(user));
}
@Operation(summary = "Delete user by ID")
@DeleteMapping("/{id}")
@CacheEvict(value = "users", key = "#id")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
logger.warn("Deleting user with ID: {}", id);
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}
7. Enable Redis Caching
package com.example.api.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
}
8. Enable Swagger UI
Visit http://localhost:8080/swagger-ui.html
to test API endpoints.
9. Enable Actuator for Monitoring
Add this to application.yml
:
management:
endpoints:
web:
exposure:
include: health,metrics
Now access monitoring: http://localhost:8080/actuator/health
Conclusion
Versioning
Exception Handling
Security (Placeholder)
Logging
Caching
Pagination, Sorting, Filtering
Swagger Docs
This is a production-ready API. Want to extend it with JWT Authentication or Rate Limiting? Let me know!
This content originally appeared on DEV Community and was authored by DevCorner