Я сделал иначе в итоге. Эксепшены рулятся в перехватчике, который респонс конструирует и отдает
А если не эксепшен, а просто фигня (поиск по id не дал результатов), то из сервиса в контроллер тоже прокидываю нулл, а в контроллере уже проверяю и конструирую либо норм респонс, либо респонс с инфой об ошибке
В итоге глобальный перехватчик работает с ошибками, бросаемыми самим спрингом, а в остальном контроллер по данным, пришедшим от сервиса, уже генерит респонс с инфой об ошибке или валидный респонс