Почему нельзя использовать @RequestBody с GET методом в Spring
Что проверяют
Понимаете ли вы семантику HTTP-методов и то, как Spring разбирает запрос, а не только помните ли, что «так не делают». Хороший ответ отделяет запрет протокола от ограничений клиентов и инфраструктуры.
Как отвечать
Формально тело у GET не запрещено, но у него нет определённой семантики: спецификация прямо говорит, что содержимое в GET может привести к тому, что запрос отклонят. Дальше начинается практика, и она жёстче спецификации.
Кэширование: ответ на GET кэшируется по адресу и заголовкам, тело в ключ кэша не входит. Два запроса с разными телами получат один ответ из кэша браузера, прокси или CDN. Это не «иногда странно работает», это гарантированно неверные данные.
Клиенты: HttpURLConnection тело для GET просто не отправит, многие прокси и балансировщики его срезают, консольные клиенты требуют явного флага. Сервер получит пустой поток, и Spring упадёт с HttpMessageNotReadableException про отсутствующее тело, хотя вызывающая сторона уверена, что данные отправила.
Сам Spring MVC при этом препятствий не чинит: @RequestBody на GET-методе скомпилируется и заработает, если тело всё же доехало. Именно поэтому ловушка живучая: локально всё зелёное, а через реальную цепочку нет.
Что делать: простые параметры через @RequestParam или @ModelAttribute, сложный фильтр через отдельный POST-эндпоинт поиска с телом, идентификаторы через путь.
Где спотыкаются
Путают безопасность и идемпотентность. GET обязан быть безопасным, то есть не менять состояние; идемпотентность это про одинаковый результат при повторе, и ей удовлетворяют также PUT и DELETE.
Второе: заявляют, что тело в GET запрещено стандартом. Собеседник, читавший спецификацию, поправит, и весь ответ обесценится. Достаточно сказать, что семантики нет и поведение реализаций не определено.
Третье: чинят симптом через @RequestBody(required = false). Исключение уходит, вместо него приходит null, а NPE вылезает тремя строками ниже. Проблему передачи данных это не решает.