Серия: агент против легаси. Миграция и дефекты.

Четвёртый пост серии, самая неожиданная находка из всех. Мигрирую тот же backend со Spring Boot 2.2/Java 11 на Spring Boot 3.5/Java 21. Защитная сетка — 130 тестов из прошлого поста, включая 12 штук с пометкой «известный баг». Правило простое: тесты не подгоняются под новое поведение, любое расхождение — стоп и разбор.

Первая ловушка — ещё до самой миграции. План был изолировать переменные: сначала JDK 11→21 на старом фреймворке, потом уже Spring Boot 3 — так проще понять, что именно вызвало эффект. На практике не сработало: ASM в Spring 5.2 не умеет читать classfile от JDK 21, и 57 тестов с @SpringBootTest просто не стартуют — защитная сетка пропадает ровно там, где она нужнее всего. Пришлось вставить промежуточный хоп на Spring Boot 2.7.18, чтобы получить честную матрицу 2×2, а не иллюзию изоляции.

После обновления — 44 упавших теста. 41 из них оказались инфраструктурным шумом: Hibernate 6 иначе упорядочивает колонки, изменилась легальность паттернов путей, у jjwt пропала транзитивная зависимость. Это разбирается методично и скучно.

А один — не шум. Три репозитория с самого начала были объявлены с неверным generic-типом ID — баг, который на Spring Boot 2 бил громко: InvalidDataAccessApiUsageException при любом вызове, всегда, без исключений. После миграции этот тест… прошёл. Не потому что баг исчез. Hibernate 6 стал сговорчивее — начал молча приводить типы там, где раньше кидал исключение.

И тут выяснилось интересное: findById("2147483641") теперь реально возвращает пользователя. А findById(1) — тихо отдаёт пустой Optional. Падает только откровенный мусор вроде "nonsense". Дефект не исчез — он сменил режим отказа: с громкого и предсказуемого на тихий и зависящий от того, какие именно данные пришли.

Разработчик, который смотрит на зелёные тесты после миграции, видит подтверждение, что всё в порядке. Прод в какой-то момент получит первый нечисловой ID и упадёт там, где раньше падал всегда и сразу — то есть там, где раньше баг было невозможно не заметить на первом же тестовом прогоне.

Что с этим делать. Приоритет исправления этого бага я после миграции не понизил, а поднял. Симптом стал реже — это не значит, что стало безопаснее.

Общий вывод на пост, а не только на этот баг: после обновления фреймворка мало смотреть, сколько тестов позеленело. Нужно отдельно смотреть на список известных дефектов и спрашивать не «работает ли ещё», а «не стал ли режим отказа тише». Это ровно то, что тестами по умолчанию не проверяется — зелёный тест на баг с изменившимся поведением выглядит точно так же, как зелёный тест на исправленный баг.