Spring Boot
02 / 02

Validation, Error Handling & Testing

Validation, Error Handling & Testing

Validation & Global Error Handling

public record CreateUserRequest(
  @NotBlank String name,
  @Email String email
) {}

@RestControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(MethodArgumentNotValidException.class)
  public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
    return ResponseEntity.badRequest().body(new ErrorResponse("Validation failed", ex.getMessage()));
  }

  @ExceptionHandler(UserNotFoundException.class)
  public ResponseEntity<ErrorResponse> handleNotFound(UserNotFoundException ex) {
    return ResponseEntity.status(404).body(new ErrorResponse("Not found", ex.getMessage()));
  }
}
// Centralizes error formatting — no repeated try/catch in every controller

Avoiding N+1 Queries

// N+1 problem: 1 query for orders, then N more for each order.customer
// (lazy loading triggered inside a loop)
for (Order order : orderRepository.findAll()) {
  System.out.println(order.getCustomer().getName()); // separate query per order!
}

// Fix — JOIN FETCH pulls the association in the SAME query
@Query("SELECT o FROM Order o JOIN FETCH o.customer")
List<Order> findAllWithCustomer();

Testing Slices

// Fast — loads only the web layer, service is mocked
@WebMvcTest(UserController.class)
class UserControllerTest {
  @Autowired MockMvc mockMvc;
  @MockBean UserService userService;

  @Test
  void getUser_returnsUserJson() throws Exception {
    when(userService.findById(1L)).thenReturn(new UserDto(1L, "Alice"));
    mockMvc.perform(get("/api/users/1"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.name").value("Alice"));
  }
}

// Loads only JPA-related beans against a test database
@DataJpaTest
class UserRepositoryTest {
  @Autowired UserRepository userRepository;

  @Test
  void findByEmail_returnsMatchingUser() {
    userRepository.save(new User("a@example.com", "Alice"));
    assertThat(userRepository.findByEmail("a@example.com")).isPresent();
  }
}

// Full context — closest to a real integration test, slowest to start
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserApiIntegrationTest { /* ... */ }

Actuator

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,metrics,info

# GET /actuator/health — wired into Kubernetes liveness/readiness probes
# GET /actuator/metrics — JVM, HTTP, and custom application metrics

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free