The Library API uses standard HTTP status codes to indicate the success or failure of requests. Controllers return ResponseEntity objects with appropriate status codes and error messages.
@GetMapping(path = "/users/{id}")public ResponseEntity<UserDto> getUser(@PathVariable("id") Long id) { Optional<UserEntity> foundUser = userService.findOne(id); return foundUser.map(userEntity -> { UserDto userDto = userMapper.mapTo(userEntity); return new ResponseEntity<>(userDto, HttpStatus.OK); }).orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));}
The pattern Optional.map(...).orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND)) is consistent across all GET operations for single resources.
@PostMapping("/auth/login")public ResponseEntity<String> login(@RequestBody UserDto userDto) { Optional<UserEntity> possibleUser = userService.findByEmail(userDto.getEmail()); if (!possibleUser.isPresent()) { return new ResponseEntity<>("Invalid email or password", HttpStatus.UNAUTHORIZED); } UserEntity existingUser = possibleUser.get(); if (existingUser == null || !passwordEncoder.matches(userDto.getPassword(), existingUser.getPassword())) { return new ResponseEntity<>("Invalid email or password", HttpStatus.UNAUTHORIZED); } return new ResponseEntity<>("Login successful", HttpStatus.OK);}
The error message “Invalid email or password” is intentionally vague to prevent user enumeration attacks. It doesn’t reveal whether the email exists or if only the password was wrong.
@PostMapping(path = "/books/{isbn}/user/{user_id}")public ResponseEntity<RentalDto> rentBook(@PathVariable("user_id") Long user_id, @PathVariable("isbn") String isbn) { if (!bookService.isExists(isbn) || !userService.isExists(user_id)) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } BookEntity bookEntity = bookService.findOne(isbn).get(); if (bookEntity.getStock() > 0) { bookEntity.setStock(bookEntity.getStock() - 1); bookService.partialUpdate(isbn, bookEntity); } else { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } // ... continues with rental creation}
The insufficient stock error returns only a status code without a descriptive message. Consider adding an error message like “Book out of stock” for better client experience.
The API follows an idempotent DELETE pattern where deleting a non-existent resource is not treated as an error. This simplifies client logic but makes it impossible to distinguish between successful deletion and deletion of non-existent resources.
@Overridepublic UserEntity partialUpdate(Long id, UserEntity userEntity) { userEntity.setId(id); return userRepository.findById(id).map(existingUser -> { Optional.ofNullable(userEntity.getFirstname()).ifPresent(existingUser::setFirstname); Optional.ofNullable(userEntity.getLastname()).ifPresent(existingUser::setLastname); Optional.ofNullable(userEntity.getPassword()).ifPresent(existingUser::setPassword); Optional.ofNullable(userEntity.getGoogleId()).ifPresent(existingUser::setGoogleId); Optional.ofNullable(userEntity.getDisplayPicture()).ifPresent(existingUser::setDisplayPicture); return userRepository.save(existingUser); }).orElseThrow(() -> new RuntimeException("User does not exist"));}
This RuntimeException would result in an HTTP 500 error if the controller didn’t check userService.isExists(id) first. Controllers should always validate resource existence before calling partial update methods.