Что происходит, если по userId пользователь не найден?
Что проверяют
Вопрос выглядит мелочью, но за ним стоит умение проектировать контракт метода и границы слоёв. Смотрят, различаете ли вы «данных нет» и «произошёл сбой», как это различие выражено в сигнатуре и во что превращается на границе HTTP.
Как отвечать
Начните с репозитория. Spring Data возвращает Optional<User>, и это верный контракт: отсутствие строки здесь ожидаемый исход, а не авария. Решение, что с ним делать, принимает сервис, а не репозиторий.
Дальше разделите случаи. Если отсутствие пользователя нарушает инвариант вызывающего кода, например идентификатор пришёл по внешнему ключу из своей же таблицы, бросаем доменное исключение. Если пользователь ищется по внешнему вводу, пустой результат это нормальный ответ, и наружу он уходит без исключения.
public User getById(long userId) {
return repository.findById(userId)
.orElseThrow(() -> new UserNotFoundException(userId));
}На границе HTTP исключение переводится в статус одним общим обработчиком с @RestControllerAdvice, а не блоком try/catch в каждом контроллере. Запрос одного ресурса, которого нет, это 404; выборка коллекции по фильтру, под который ничего не попало, это 200 с пустым списком. В теле отдаём стабильный машинный код ошибки, а не только текст: клиент не должен разбирать сообщение регуляркой.
Где спотыкаются
Возвращают null и оставляют вызывающему угадывать, что это значит. Через два слоя это превращается в NullPointerException в неожиданном месте, и по стектрейсу уже не видно, что исходной причиной была пустая выборка.
Пишут связку isPresent и get вместо orElseThrow или map. Работает, но Optional тут выступает обёрткой над проверкой на null, хотя его смысл ровно в том, чтобы отсутствие нельзя было молча забыть.
Путают findById и getReferenceById (в старых версиях getOne). Второй в базу не ходит и отдаёт прокси, поэтому отсутствие строки вылезет позже, при первом обращении к полю, возможно уже вне транзакции и совсем другим исключением.