> Что происходит, если по userId пользователь не найден? (Java)

Уровень: senior · Роль: backend · Язык: Java · Категория: Технические вопросы

Компании: Северсталь

Стек: Java

> Пример ответа

Короткий ответ

Если пользователь по userId не найден, поведение зависит от контракта API и реализации. В типичном Java-приложении это либо выбрасывание исключения (например, UserNotFoundException), либо возврат Optional.empty(), либо null. Для REST API корректный ответ - HTTP 404 с телом ошибки. Внутри сервиса предпочтительно использовать Optional или исключение, чтобы избежать NullPointerException и явно сигнализировать о нарушении инварианта.

Подробное объяснение

В Java-бэкенде сценарий "пользователь не найден" обрабатывается на нескольких уровнях:

  1. Репозиторий/DAO: Spring Data JPA возвращает Optional<User> из findById(), что заставляет вызывающий код явно обработать отсутствие значения. Если использовать getOne() или findById().orElse(null), появляется риск NPE.

  2. Сервисный слой: здесь принимается решение о бизнес-логике. Два основных подхода:

    • вернуть Optional<User> наверх - тогда контроллер решает, что делать;
    • выбросить доменное исключение UserNotFoundException - тогда его перехватит @ControllerAdvice и преобразует в HTTP-ответ.
  3. Контроллер/REST: для REST API стандарт - статус 404 Not Found. Тело ответа обычно содержит структуру ошибки: timestamp, статус, message, path. Это реализуется через ResponseEntity или глобальный обработчик исключений.

  4. Граничные случаи: если userId приходит как null или невалидный (например, отрицательное число), это отдельная ошибка валидации (400 Bad Request), а не 404. Также важно различать "не найден" и "нет доступа" (403) - если пользователь существует, но недоступен текущему субъекту.

  5. Производительность и безопасность: не стоит раскрывать в ответе детали, почему не найден (например, "user with id 123 deleted"), чтобы не давать информацию для атаки. Достаточно generic-сообщения.

На практике

В реальном коде чаще всего используется связка:

  • UserRepository.findById(id) → Optional<User>;
  • в сервисе: userRepository.findById(id).orElseThrow(() -> new UserNotFoundException(id));
  • глобальный @RestControllerAdvice с @ExceptionHandler(UserNotFoundException.class) возвращает ResponseEntity со статусом 404 и телом ошибки.

Для внутренних вызовов между микросервисами (например, через Feign) - аналогично: клиент должен обработать 404 и либо пробросить исключение, либо вернуть Optional. В Kafka-консьюмерах или batch-задачах отсутствие пользователя часто логируется и пропускается, но не роняет весь процесс.

Валидация входного userId: если он null или не соответствует формату (UUID, положительное число), это обрабатывается на уровне @Valid или явной проверкой до обращения к репозиторию.

Пример кода

JAVA
// Сервис
@Service
public class UserService {
private final UserRepository userRepository;
public User getUserById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException(userId));
}
}
// Исключение
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(Long userId) {
super("User not found: " + userId);
}
}
// Глобальный обработчик
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ApiError> handleUserNotFound(UserNotFoundException ex) {
ApiError error = new ApiError(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}
// Контроллер
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@GetMapping("/{userId}")
public UserDto getUser(@PathVariable Long userId) {
return UserMapper.toDto(userService.getUserById(userId));
}
}

Как отвечать на собеседовании

Начните с того, что поведение зависит от контекста: REST API, внутренний сервис, batch. Затем опишите цепочку: репозиторий → сервис → контроллер. Упомяните Optional как способ избежать NPE, и @ControllerAdvice для единообразной обработки ошибок. Подчеркните разницу между 404 и 400, а также между "не найден" и "нет доступа". Если спросят про альтернативы - скажите про возврат null (плохо), Optional (хорошо для внутреннего использования), исключение (хорошо для нарушения инварианта). Добавьте про логирование и безопасность: не раскрывать лишние детали в ответе.

Что проверяет интервьюер

  • Понимание работы с Optional и исключениями в Java.
  • Умение проектировать REST API: корректные HTTP-статусы.
  • Знание паттерна глобальной обработки ошибок в Spring.
  • Осознание разницы между ошибками валидации и отсутствием ресурса.
  • Способность рассуждать о граничных случаях и безопасности.
  • Уровень владения идиоматичным Java-кодом без NPE.

Типичные ошибки

  • Возврат null из метода без документации - источник NPE.
  • Использование getOne() из Spring Data JPA, который бросает EntityNotFoundException лениво и неочевидно.
  • Игнорирование разницы между 404 и 400: если userId невалиден, это не "не найден".
  • Отсутствие @ControllerAdvice - каждый контроллер дублирует обработку ошибок.
  • Проброс NoSuchElementException из Optional.get() без контекста - непонятно, что именно не найдено.
  • Раскрытие в ответе внутренних деталей (например, SQL-запрос или причину удаления) - риск безопасности.
  • В batch-обработке: падение всего процесса из-за одного отсутствующего пользователя вместо логирования и продолжения.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью