# Building Scalable REST APIs with Spring Boot

> A comprehensive guide to designing and implementing RESTful APIs using Spring Boot, covering best practices for authentication, validation, and error handling.

- **Author:** Utsab Dahal, Backend Developer — Spring Boot & DevOps
- **Filed:** 15 January 2024 (2024-01-15)
- **Section:** Spring Boot
- **Reading time:** 8 min read
- **Tags:** Spring Boot, REST API, Java, Backend
- **Canonical page:** https://dahalutsab.com.np/dispatches/building-scalable-rest-apis-spring-boot

---

REST APIs are the backbone of modern web applications. In this comprehensive guide, we'll explore how to build scalable, maintainable REST APIs using Spring Boot.

## Introduction

Spring Boot has revolutionized Java development by providing a convention-over-configuration approach that allows developers to quickly create production-ready applications. When it comes to building REST APIs, Spring Boot offers powerful features that make development both efficient and enjoyable.

### Why Spring Boot for REST APIs?

- **Auto-configuration**: Minimal setup required
- **Embedded servers**: No need for external application servers
- **Production-ready features**: Health checks, metrics, and monitoring
- **Rich ecosystem**: Extensive library support
- **Developer experience**: Hot reloading and excellent tooling

## Project Setup

Let's start by creating a new Spring Boot project with the necessary dependencies.

### Maven Dependencies

```xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>
```

### Application Properties

Configure your database connection and other settings:

```properties
spring.datasource.url=jdbc:mysql://localhost:3306/api_db
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
```

## Creating Entities

Let's create a User entity as an example:

```java
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    @Email
    private String email;
    
    @Column(nullable = false)
    @Size(min = 2, max = 50)
    private String firstName;
    
    @Column(nullable = false)
    @Size(min = 2, max = 50)
    private String lastName;
    
    @CreationTimestamp
    private LocalDateTime createdAt;
    
    @UpdateTimestamp
    private LocalDateTime updatedAt;
    
    // Constructors, getters, and setters
}
```

### Repository Layer

Create a repository interface:

```java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
    List<User> findByFirstNameContainingIgnoreCase(String firstName);
}
```

## Building Controllers

Now let's create a REST controller:

```java
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        List<User> users = userService.getAllUsers();
        return ResponseEntity.ok(users);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return ResponseEntity.ok(user);
    }
    
    @PostMapping
    public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
        User createdUser = userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(
            @PathVariable Long id, 
            @Valid @RequestBody User user) {
        User updatedUser = userService.updateUser(id, user);
        return ResponseEntity.ok(updatedUser);
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}
```

### Service Layer

Implement the business logic:

```java
@Service
@Transactional
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found with id: " + id));
    }
    
    public User createUser(User user) {
        if (userRepository.findByEmail(user.getEmail()).isPresent()) {
            throw new EmailAlreadyExistsException("Email already exists: " + user.getEmail());
        }
        return userRepository.save(user);
    }
    
    public User updateUser(Long id, User userDetails) {
        User user = getUserById(id);
        user.setFirstName(userDetails.getFirstName());
        user.setLastName(userDetails.getLastName());
        user.setEmail(userDetails.getEmail());
        return userRepository.save(user);
    }
    
    public void deleteUser(Long id) {
        User user = getUserById(id);
        userRepository.delete(user);
    }
}
```

## Error Handling

Implement global exception handling:

```java
@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.NOT_FOUND.value(),
            ex.getMessage(),
            System.currentTimeMillis()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
    
    @ExceptionHandler(EmailAlreadyExistsException.class)
    public ResponseEntity<ErrorResponse> handleEmailAlreadyExists(EmailAlreadyExistsException ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.CONFLICT.value(),
            ex.getMessage(),
            System.currentTimeMillis()
        );
        return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationErrors(MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.toList());
            
        ErrorResponse error = new ErrorResponse(
            HttpStatus.BAD_REQUEST.value(),
            "Validation failed: " + String.join(", ", errors),
            System.currentTimeMillis()
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
}
```

## Testing

Write comprehensive tests for your API:

```java
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@TestPropertySource(locations = "classpath:application-test.properties")
class UserControllerTest {
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void shouldCreateUser() {
        User user = new User();
        user.setFirstName("John");
        user.setLastName("Doe");
        user.setEmail("john.doe@example.com");
        
        ResponseEntity<User> response = restTemplate.postForEntity(
            "/api/users", user, User.class);
        
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(response.getBody().getEmail()).isEqualTo("john.doe@example.com");
    }
    
    @Test
    void shouldReturnUserById() {
        User savedUser = userRepository.save(createTestUser());
        
        ResponseEntity<User> response = restTemplate.getForEntity(
            "/api/users/" + savedUser.getId(), User.class);
        
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody().getId()).isEqualTo(savedUser.getId());
    }
}
```

## Best Practices

### 1. Use DTOs for Data Transfer

Create separate DTOs for request and response:

```java
public class UserCreateRequest {
    @NotBlank
    @Email
    private String email;
    
    @NotBlank
    @Size(min = 2, max = 50)
    private String firstName;
    
    @NotBlank
    @Size(min = 2, max = 50)
    private String lastName;
    
    // getters and setters
}
```

### 2. Implement Pagination

```java
@GetMapping
public ResponseEntity<Page<User>> getAllUsers(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size,
        @RequestParam(defaultValue = "id") String sortBy) {
    
    Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
    Page<User> users = userService.getAllUsers(pageable);
    return ResponseEntity.ok(users);
}
```

### 3. Add API Documentation

Use Swagger/OpenAPI:

```java
@RestController
@RequestMapping("/api/users")
@Tag(name = "User Management", description = "APIs for managing users")
public class UserController {
    
    @Operation(summary = "Get all users", description = "Retrieve a paginated list of all users")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "Successfully retrieved users"),
        @ApiResponse(responseCode = "500", description = "Internal server error")
    })
    @GetMapping
    public ResponseEntity<Page<User>> getAllUsers(/* parameters */) {
        // implementation
    }
}
```

## Conclusion

Building scalable REST APIs with Spring Boot involves following best practices for project structure, error handling, validation, and testing. By leveraging Spring Boot's powerful features and following these guidelines, you can create robust, maintainable APIs that scale with your application's needs.

Key takeaways:
- Use proper layered architecture (Controller → Service → Repository)
- Implement comprehensive error handling
- Add validation at multiple levels
- Write thorough tests
- Use DTOs for clean data transfer
- Document your APIs properly
- Consider pagination for large datasets

With these foundations in place, your Spring Boot REST APIs will be well-equipped to handle production workloads and future growth.

---

Source: https://dahalutsab.com.np/dispatches/building-scalable-rest-apis-spring-boot
From The Utsab Dahal Times — the record of Utsab Dahal, Backend Developer — Spring Boot & DevOps, Kathmandu, Nepal.
Full record: https://dahalutsab.com.np/llms-full.txt
