Как решать конфликты библиотек в Spring Boot?
Что проверяют
Знаете ли вы, как Maven и Gradle выбирают версию при конфликте и какую роль в этом играет BOM Spring Boot. Следом почти всегда спрашивают, как понять, что NoSuchMethodError в рантайме это конфликт версий.
Как отвечать
Сначала механизм. Spring Boot управляет версиями через BOM spring-boot-dependencies: он подключается через parent spring-boot-starter-parent или импортом в dependencyManagement, в Gradle через platform(...) или плагин io.spring.dependency-management. Поэтому у стартеров версии не пишут, а набор библиотек проверен на совместимость.
Когда конфликт всё же возникает, правила у сборщиков разные:
- Maven: побеждает ближайшая к корню версия, при равной глубине первая объявленная. Это не обязательно самая новая;
- Gradle: по умолчанию выбирает самую высокую версию из запрошенных.
Диагностика:
- дерево:
mvn dependency:tree -Dverbose -Dincludes=группа:артефактпоказывает, откуда пришла версия и какие были отброшены. В Gradle то же делает./gradlew dependencyInsight --dependency имя --configuration runtimeClasspath; - симптомы:
NoSuchMethodError,NoClassDefFoundError,AbstractMethodErrorпри старте или в конкретном вызове; - контроль: правило
dependencyConvergenceв Maven Enforcer илиfailOnVersionConflict()в Gradle ловят расхождения на сборке, а не на проде.
Способы решения по порядку предпочтения: переопределить свойство версии из BOM, например <jackson-bom.version> в properties, исключить транзитивную зависимость через exclusions или exclude, подключить нужную версию явно. Типичный случай: переход на Jetty через исключение spring-boot-starter-tomcat. Для Spring Boot 3 и новее отдельная боль это библиотеки на старом javax.* вместо jakarta.*.
Где спотыкаются
Думают, что Maven всегда берёт свежую версию. Из-за правила ближайшей зависимости старая версия спокойно побеждает новую.
Прописывают версию поверх BOM, не проверив совместимость. Свежий Jackson или Hibernate может сломать автоконфигурацию, которая рассчитывала на протестированный набор.
Исключают библиотеку и не добавляют замену. Сборка проходит, а приложение падает при первом обращении к классу.