Когда начинается разговор про Java, почти всегда он внезапно уходит в сторону Spring. И это не случайность.

Проблемы с релизами, поддержкой, миграциями, безопасностью — это не «проблемы Spring». Это следствие того, как изменилась сама Java-экосистема.

Чтобы понять, что происходит со Spring, нужно сначала разобраться, что произошло с Java.

Этот текст вырос из митап-дискуссии. Если вы больше любите смотреть видео, то смотрите запись доклада на YouTube или VK Видео.


Мы собрали картину шире: как ускорение релизов Java вместе с короткими окнами OSS-сопровождения Spring меняют правила игры для долгоживущих корпоративных систем. Вопрос тут не в том, «какая версия правильная», а в ответственности: кто и как будет отвечать за обновления, безопасность и совместимость, когда продукт живёт годами, а релизный ритм измеряется месяцами. Выбор Java LTS и  мажорные LTS-версии Spring Framework/Spring Boot часто выглядят как простое решение. Однако на практике это только старт разговора, потому что дальше важны политика поставщика, модель сопровождения и реальная способность команды жить в этом цикле.

Что будет в статье:

  • Где возникает путаница между LTS, обновлениями и поддержкой (support).

  • Почему ежегодные миграции превращаются в отдельный вид работ.

  • Как выбрать модель ответственности для платформенной команды и не загнать разработку в «гонку релизов».

Вы не используете OpenJDK

Что такое вообще Java? И чем она отличается от проекта OpenJDK? Java — это язык программирования, и, грубо говоря, набор спецификаций: спецификации виртуальной машины, спецификация языка. Существует некоторая базовая  имплементация, которая есть на GitHub и которая разрабатывается сообществом OpenJDK.

OpenJDK — это исходный код, у него нет какой-то официальной сборки.

Если вы думаете, что вы используете OpenJDK, то нет, вы его не используете. Если у вас есть бинарник, то это уже не OpenJDK. Вы используете дистрибутив, построенный из исходников OpenJDK.

OpenJDK не существует. Это вам кажется, это обман, фикция.

Amazon Corretto, Eclipse Temurin, Axiom JDK, Liberica и другие — это всё примеры дистрибутивов. Дистрибутив — бинарная сборка JDK на основе исходников OpenJDK, подготовленная конкретным поставщиком, вместе с определённым набором сертификатов, потенциально с определённым набором нативных библиотек, с определённым также набором, возможно, каких-то пропатченных вещей и так далее. Это такой полный набор, который соответствует спецификациям, написанным в Java-платформе и так далее.

Вы все время используете конкретный дистрибутив, даже если вы этого не знаете.

И здесь появляется ключевой момент, который часто упускают.

Поддержка/Support  — это не свойство версии или дистрибутива.
Поддержка — это договор между вендором и клиентом.

Если нет контракта, никто не обязан рассматривать ваши обращения и помогать вам решать проблемы. Есть только апстрим (upstream) и его правила.

Эта логика полностью переносится в мир Spring.

Что на самом деле означает LTS?

«Java 25 — это LTS, и хватит выдумывать…»

В процессе подготовки статьи у нас в команде возник спор: не про то, нужна ли длительная поддержка (тут позиция единая), а про термин LTS. Он слишком удобный, поэтому им часто называют разные вещи и дальше начинается холивар.

Вот такое у нас было обсуждение:

Для любого нормального человека Java 25 — это LTS. Это написано у вендоров, это так называют на рынке. А вы тут занимаетесь корпоративным булшитом и подменой понятий.

Да, метка LTS у релиза есть. Но она не отвечает на главный вопрос: кто и в каких условиях будет выпускать обновления и разбирать ваши инциденты. Это уже не “про релиз”, это про поставщика и уровень поддержки.

В апстриме есть ветки обновлений и бэкпорты. Но в продакшене вы живёте не в апстриме, а на конкретной сборке. И дальше всё упирается в политику и обязательства того, кто эту сборку поставляет.

Путаница начинается, когда LTS используют как универсальный термин. А на деле это минимум три разных вещи: метка релиза, окно обновлений и support как услуга с обязательствами, и это разные вещи для Java, Spring и Linux


Итог спора (спойлер статьи):

Java 25 — LTS по роадмапам. Но LTS — это ярлык, а реальность задаёт поставщик: сроки апдейтов и уровень поддержки. Поэтому дальше  в статье мы разводим термины и обсуждаем ответственность, а не “игру в слова”.

Поэтому предлагаем читать дальше и делиться в комментариях: для вас LTS это что: метка релиза, срок получения апдейтов, или наличие поддержки с обязательствами?

Впервые LTS появился в Ubuntu. В какой-то момент Ubuntu стали маркировать релизы как LTS. Примерно в тот же момент в ядре Linux случилось то же самое.

Когда мы говорим про Linux, а именно LTS, то там нигде не написано, что это long-term support.

Там есть стабильная ветка, которая аналогична текущей Java. А есть какие-то ядра, которые поддерживаются долго, но слово поддержка они не употребляют. Они говорят long-term stable. В этом смысле Linux более честен, чем Java. По той причине, что есть какая-то версия ядра, в которую постоянно добавляются всякие security-патчи и, может быть, какие-то фиксы производительности.

А есть Java, где написано long-term support. Также есть какие-то версии Java, в которых в течение нескольких лет будут добавляться security-патчи.

В чём суть отношений между вендором и заказчиком? LTS в Linux — это long-term stable, а в Java — это long-term support. Так вот support — это не свойство программы,  не свойство дистрибутива. Support — это отношения между каким-то поставщиком и каким-то клиентом.

Support — это когда ты можешь в 03:00 ночи позвонить и сказать: “Вы знаете, мне не нравится, как у меня Java работает. Мне кажется, должно быть как-то по другому”. Тебе будут объяснять, что всё нормально, все хорошо, будут успокаивать. А когда поймут, что у тебя действительно какая-то проблема, то по твоему требованию её исправят, даже если эта проблема не исправлена в текущем релизе. Вот что значит support.

У нас получается так, что в LTS словом support называют просто некоторый maintenance,  когда у нас есть отдельный проект JDK updates, в который периодически засылают security-патчи. Кстати говоря, это не те люди, которые делали первый релиз версии Java, это уже другая команда.

Итак, в чём двойственность термина support? Проблема в том, что в какой-то момент решается вопрос того, что будет опубликована новая версия OpenJDK (17, 19, 20, 22, неважно), и часть из этих версий — LTS. Если версия маркируется LTS, то это означает, что состояние OpenJDK на этот коммит отдаётся в отдельный проект OpenJDK support. OpenJDK — это, на самом деле, большая инициатива. В OpenJDK много проектов: Leyden, Valhalla и другие. Над этими проектами работают разные люди. Есть отдельный проект, который занимается исключительно небольшими патчами. Если версия маркируется как LTS в Java, то на полгода в проект ставится определенный человек, лид, который будет потенциально вносить какие-то минорные багфиксы, но не более.

Это значит, что, если у вас появятся вопросы или предложения по изменениям, то нет никакой уверенности, что это кто-то поправит.

С не-LTS версиями всё ещё хуже. Если через полгода у LTS-версий есть конкретные люди, которые будут пытаться что-то исправить и не то, что вам нужно, то для не-LTS версий это не так.

На самом деле, LTS — это не сама платформа Java, это отношения конкретного клиента с конкретным вендором. Вы покупаете поддержку у конкретного вендора.

Нет контракта — нет поддержки.

А как это всё соотносится с Spring? Spring стремится поддерживать релизный цикл Java. Например, когда вышел Spring Boot 3.0, то требовалась версия Java не ниже 17.

LTS — это срок жизни ветки.
Support — это коммерческое обязательство.

Путаница между этими понятиями — источник большинства иллюзий.

Как ускоренный релизный цикл изменил всё

Java когда-то перешла на регулярный быстрый цикл (полгода), и мотивация была понятная: люди устали от ощущения стагнации платформы. Spring старается поддерживать релизный цикл Java и Jakarta EE, “встраиваясь” в него.

LTS Spring — то же самое, что LTS в Java. Кто предоставляет поддержку Spring? У Spring есть коммерческая поддержка от Broadcom, но в РФ она недоступна.

Параллельно есть реальность конференций и докладов: у Джоша Лонга и в похожих докладах — всегда «самый новый Spring», «самая новая Java», «самая новая версия всего». Но в жизни это так не работает.

Ключевой вопрос звучит просто: “Успеваем ли мы за этим адаптироваться?

Ответ тоже обычно простой: «Большинство не успевает. Корпоративные и промышленные проекты живут дольше».

«Зачем нам обновляться?» — 3 категории людей и один отдел безопасности

Вопрос: «зачем обновляться» встаёт остро почти у каждого.

Очень грубое деление:

  • «Я хо��у обновиться срочно, мне нужна новая конфигурация, фичи, я пока не знаю зачем, но хочу».

  • «Я не хочу обновляться никогда, вообще в жизни, зачем оно мне».

  • «Я не хочу, но пришел начальник ИБ и сказали надо»

И вот ко второй категории однажды приходит безопасность, мягко кладёт руку на плечо и начинает перечислять уязвимости, последствия и эксплуатацию уязвимостей. И внезапно выясняется, что «вообще-то отвечаешь ты, как разработчик». И дальше уже разговор не «хочу/не хочу», а «идём обновляться прямо сейчас».

Формально вас никто не заставляет обновляться.

Практически — вас заставляют:

  • безопасность,

  • инфраструктура,

  • несовместимость зависимостей.

Теперь про главное: короткая поддержка релизов и конец обновлений Spring Boot 3.5

Вот тут и начинается боль.

У нас есть Spring, выходит новый релиз. Он будет поддерживаться 13 месяцев, то есть чуть больше года.

За эти «чуть больше года» обычно успеваешь что-то разработать (условно — новый сервис). И после этого по-хорошему:

  • миграция на следующую версию,

  • потом на следующую,

  • потом ещё.

Постоянная миграция выматывает, особенно когда она не “поднял версию и поехали”, а реальная работа по совместимости, зависимостям, поведению.

И дальше происходит то, ради чего всё это обсуждение вообще имеет смысл: Кто-то сейчас находится на старой ветке. Он может перейти, допустим, на 3.5, но 3.5 тоже перестанет обновляться. Можно обновляться с 2.7 сразу на 3.5. А после этого, по идее, нужно будет переходить на четвёртый. Вопрос — на какой: 4.0? 4.1?

В реальности:

  • у вас сотни сервисов,

  • внутренняя платформа,

  • собственные стартеры,

  • слой обёрток поверх Spring,

  • корпоративные требования к совместимости.

И мигрировать, получается, нужно раз в год.

Если сервисов много, это превращается в отдельный вид спорта. Около 300 сервисов — это больно. А ещё есть компании с экосистемой в 3000.

Отсюда нормальное человеческое желание: хотелось бы заниматься этим, например, раз в четыре года, а не каждый год. Миграции между мажорными версиями не бывают бесшовными.

Команды начинают тратить ресурсы не на развитие продукта, а на поддержание соответствия релизному графику. Релизный цикл сам становится источником технического долга.

«Окей, останемся на старой ветке» — и тут прилетает безопасность

Самый неприятный триггер — безопасность. Если вдруг находится серьёзная уязвимость, сценарий может быть простой: зависимость удаляют из внутреннего репозитория, а при первой же сборке — живи как хочешь, разбирайся.

Дальше у вас развилка:

  • либо откатиться/заморозиться,

  • либо идти вперёд (а вперёд — это часто «поменялось вообще всё»: зависимости, совместимость).

И в этот момент уже не обсуждается «хочу/не хочу обновляться». В этот момент обсуждается «как вообще продолжать собираться и выпускаться».

А давайте сами всё пофиксим в апстриме

Да, можно писать фиксы в Spring. Но реальность такая: вы делаете pull request. Он может очень долго быть в рассмотрении, а релизы выходят по своему расписанию.

Если фикс нужен здесь и сейчас, то начинается жизнь с локальными патчами:

  • внести патч в исходники,

  • собрать собственную сборку Spring,

  • изменить версию артефакта (чтобы отличалась от официальной),

  • опубликовать артефакт во внутреннем репозитории (Nexus Artifactory),

  • донести до команд и зафиксировать в зависимостях, что теперь используется эта внутренняя сборка и её версия.

Потом выходит новая версия Spring и всё повторяется, только не с одним патчем, а со всеми, коих может быть много. Ещё совсем не факт точнее вообще факт, что патч не вступит в конфликт с официальным Spring)

Обычно небольшие изменения стараются держать в рамках patch-версий — «совсем немножко», без ломания совместимости. Но даже если изменения маленькие — процесс их сопровождения всё равно остаётся процессом.

Про автоматизацию миграций: OpenRewrite

Когда сервисов/репозиториев много, миграции превращаются в рутину:

  • поднять версию общей библиотеки,

  • пройтись по десяткам репозиториев,

  • заменить аннотацию на аннотацию,

  • мигрировать JUnit 4 → JUnit 5 и подобные вещи.

Тут вспоминается инструмент OpenRewrite — проект, который сделан именно для того, чтобы упрощать миграции и рутинные массовые правки по репозиториям. Этот инструмент помогает решить довольно тривиальные миграции. OpenRewrite не поможет вам мигрировать с одной мажорной версии Spring на другую.

Когда у вас 5–10 команд, у каждой десятки сервисов, а всем нужно «одновременно сделать одинаковые изменения», то без автоматизации это превращается в отдельную дисциплину.

Что хочется в такой ситуации: ветка, которую можно поддерживать дольше

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

И здесь появляется конкретная «земная» история: есть возможность жить не в ежегодной гонке, потому что Axiom берёт ветку, поддерживает её и делает так, что ею можно пользоваться долго.

В чём на самом деле выбор

Остаётся три стратегии:

  1. Следовать апстриму полностью.
    Регулярные миграциии новые фичи “сразу”, но сроки диктует релизный цикл — свой календарь почти отсутствует.

  2. Игнорировать обновления до кризиса.
    Экономия сегодня, но риски завтра.

  3. Выстраивать собственную модель сопровождения LTS-ветки.
    Закрывать уязвимости, но не менять мажорную версию ежегодно.

Ни одна стратегия не идеальна. Вопрос не в том, «какая версия лучше».

Вопрос в том:

  • Готовы ли вы мигрировать на новые версии каждый год?

  • Кто отвечает за безопасность?

  • Есть ли у вас реальный support или только ощущение стабильности?

Итог

В экосистеме Java и Spring ускорился цикл изменений, и обновления всё чаще превращаются в миграции из-за накопления несовместимостей в Java- и Spring-экосистеме

LTS перестал быть символом спокойствия и стал просто более , по сути, обещанием более долгого окна обновлений. Support — это контракт и обязательства, а не маркировка релиза.

Корпоративные проекты физически не успевают за ежегодными мажорными миграциями.

Департамент информационной безопасности рано или поздно заставляет обновляться. И в этом контексте выбор между стандартным Spring и альтернативной моделью сопровождения — это не спор о бренде. Это выбор модели ответственности:

  • либо живёте в ритме апстрима,

  • либо строите собственный контур сопровождения,

  • либо принимаете риски.

Игнорировать проблему больше не получается — цикл стал слишком быстрым. В идеале обновляйтесь регулярно. Если регулярные миграции для вашей системы слишком дороги, нужен альтернативный путь: продуманный план сопровождения выбранной ветки с закрытием уязвимостей и понятной зоной ответственности.