UA RU
logo
Опыт      27/05/2022

Как перенести проект с Java 11 на Java 17: разбираем самые частые ошибки

Микита Земницький BLOG

Java Developer в NIX

Java 17 — новая LTS-версия — вышла на рынок почти год назад, но до сих пор не приобрела значительную популярность. О том, почему на нее следует перейти, я подробно рассказывал на конференции NIX MultiConf и в своей предыдущей статье на Highload.

Сегодня я опишу основные шаги и особенности миграции проекта с Java 11 в Java 17. Я буду пытаться рассмотреть возможные ошибки и варианты их решения.

Сразу подчеркиваю: я рассмотрю миграцию с программой Spring Boot. Это один из самых популярных фреймворков, которые используют большинство Java-разработчиков.

Правда, официальная поддержка Java 17 начинается с Spring 6 и Spring Boot 3, но эти версии еще в разработке. Последняя же версия Spring Boot — 2.6.7 . Ее и рассмотрим. Хотя она официально не поддерживается Java 17, разработчики Spring приложили много усилий, чтобы версии Spring Boot от 2.6.5 уже в определенной степени работали с новой LTS.

Как вообще мигрировать на любую Java-программу?

Для начала вам нужно в pom.xml найти properties и изменить версию с текущей на 17-ю. Это может выглядеть так:

<properties>
   <java.version>11</java.version>
</properties>

на 

<properties>
   <java.version>17</java.version>
</properties>

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

error: release version 17 not supported

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

Lombok

Самая первая ошибка — это Lombok. Так называется Java-библиотека, автоматизирующая генерацию шаблонного кода. Она может генерировать гетеры, сеттеры, конструкторы, логирование и т.д., освобождая классы от загромождения шаблонным кодом. При миграции с 11-й на 17-ю версии Java вы получаете следующую ошибку:

Англійська для початківців від Englishdom.
Для тих, хто тільки починає вивчати англійську і хоче вміти використовувати базову лексику і граматику.
Реєстрація на курс
java.lang.IllegalAccesError: class lombok.javac.apt.LombokProcessor cannot access classcom.sun.tools.javac.proseccing.JavacProcessingEnvironment

Все это касается версии Lombok 1.18.22. На момент миграции моего приложения у меня была версия 1.18.12. Чтобы понять причину этой ошибки, нужно взглянуть на JEP 396, который добавил суровую инкапсуляцию внутренних компонентов JDK по умолчанию.

Этот JEP был реализован еще в 16 версии Java, и именно из-за него у Lombok больше нет возможности использовать внутренние компоненты JDK.

Сначала разработчики Lombok решили использовать следующую команду для отмены предыдущего JEP:

--illegal-access=permit

Но это было действительно только для Java 16. В 17-й же версии эту команду убрали. Поэтому в Lombok придумали другое решение. Вам следует добавить в maven-compiler-plugin аргументы. Таким образом вы даете доступ к нужным компонентам Lombok:

<compilerArgs>
    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
    <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED</arg>
</compilerArgs>

Но вручную вносить аргументы не очень удобно. Поэтому в Lombok выпустили версию 1.18.22, в которой это делается автоматически — вам достаточно добавить эту зависимость. Для этого разработчики с помощью рефлексии изменили видимость модулей — и уже после этого у вас есть доступ.

Да, это не очень секьюрно, но все же работает.

MapStruct

MapStruct — процессор аннотаций Java для автоматического генерирования преобразователей (mapper) между Java-компонентами.

MapStruct использует сгенерированные гетеры, сеттеры и конструкторы для создания преобразователей. После обновления Lombok до версии 1.18.22 преобразователи больше не создаются именно из-за той ошибки — из-за того, что был реализован JEP 396, прибавивший строгую инкапсуляцию внутренних модулей.

Курс QA Manual (Тестування ПЗ мануальне) від Powercode academy.
Навчіться знаходити помилки та контролювати якість сайтів та додатків.
Записатися на курс

Чтобы обойти этот момент, необходимо добавить обработчик аннотаций lombok-mapstruct-bindingк maven-compiler-plugin:

<path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok-mapstruct-binding</artifactId>
    <version>0.2.0</version>
</path>

После этого все должно работать без проблем.

ASM

ASM — это среда для манипулирования байтовым кодом Java. ASM использует CGLIB, который, в свою очередь, используется Spring для AOP.

В Spring Framework AOP-прокси — это динамический прокси JDK или прокси CGLIB. Spring использует CGLIB и ASM, генерирует прокси-классы, несовместимые со средой выполнения Java 17. Spring Boot ниже 2.4 и зависит от Spring Framework 5.2, который использует версию CGLIB и ASM, несовместимую с Java 17.

Но обновление библиотек CGLIB или ASM невозможно, поскольку Spring переупаковывает ASM для внутреннего использования. То есть единственный выход из этой ситуации — обновить Spring Boot. Без этого ASM не заработает.

JUnit и отсутствующее свойство spring-boot.version

В новой версии Spring Boot spring-boot-dependenciesбыло удалено свойство spring-boot.version. Разработчики использовали ее для многих задач, скажем, чтобы исключать какую-либо зависимость.

Вот пример с исключением junit-vintage-engine:

<dependencyManagement>
  <dependencies>
     <dependecy>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <version>${spring-boot.version}</version>
         <exclusions>
            <exclusion>
              <groupId>org.junit.vintage</groupId>
              <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
         </exclusions>
     </dependecy>
  </dependencies>
</dependencyManagement>

К счастью, теперь мы можем удалить этот блок, поскольку Spring Boot 2.4 удалил Vintage Engine JUnit 5 со стартера spring-boot-starter-test.

Но если ваш проект до сих пор использует JUnit 4 и вы видите ошибки компиляции вроде java: package org.junit does not exist это потому что старый движок был удален.

Онлайн-курс Бізнес-аналіз. Basic Level від Hillel IT School.
В ході курсу студенти навчаться техніці збору і аналізу вимог, документуванню та управлінню документацією, управлінню ризиками та змінами, а також навчаться моделювати процеси і прототипуванню.
Приєднатися

Старый движок отвечает за выполнение тестов JUnit 4 вместе с тестами JUnit 5. Если вы не можете из-за каких-либо обстоятельств перенести тесты на JUnit 5, добавьте в pom следующую зависимость:

<dependecy>
   <groupId>org.junit.vintage</groupId>
 <artifactId>junit-vintage-engine</artifactId>
   <scope>test</scope>
   <exclusions>
      <exclusion>
         <groupId>org.hamcrest</groupId>
         <artifactId>hamcrest-core</artifactId>
      </exclusion>
   </exclusions>
</dependecy>

Это полностью решит описанную проблему.

Jackson

Следующая возможная проблема связана с Jackson. Jackson — это библиотека инструментов обработки данных, например, для сериализации и десериализации JSON в компонентах Java и наоборот. Она может обрабатывать много форматов данных, но чаще всего ее используют для работы с JSON.

После обновления в Spring Boot 2.6.7 возникает следующая ошибка:

java.time.OffsetDateTime not supported by default: add Module “com.fasterxml.jackson.datatype:jackson-datatype-jsr310”

Причина — модуль JSR-310 недоступен для Jackson. Из-за смены Jackson 2.12 теперь это приводит к сбою сериализации, а не к тому, чтобы Jackson сериализировался в неожиданном формате.

Есть два решения этой проблемы. Вы можете создать mapper сами. Для версии 2.10 и более поздней через JsonMapper.buider это выглядит следующим образом:

ObjectMapper mapper = JsonMapper.buider()
               .addModule(new JavaTimeModule())
               .build();

Для более старой версии это можно сделать через mapper.registerModule:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());

Но все это не очень хороший вариант, поскольку создание мапперов собственными руками требует лишнего времени и усилий. Поэтому предлагаю другой способ — попробовать автоконфигурацию Jackson.

Автоконфигурация Jackson

Spring Boot обеспечивает автоконфигурацию Jackson и автоматически объявляет полностью настроенный компонент ObjectMapper. Jackson можно настроить без определения собственного Bean-компонента ObjectMapper, используя свойства или класс Jackson2ObjectMapperBuilderCustomizer.

Курс Full-stack developer від Mate academy.
Ідеальний курс для новачків - після закінчення гарантоване працевлаштування. Ви навчитесь працювати як з фронтендом, так і з бекендом сайта. .
Отримати знижку на курс

Необходимо проверить, что модуль com.fasterxml.jackson.datatype:jackson-datatype-jsr310 находится в дороге к классам (т.е. в classpath) — и он будет автоматически зарегистрирован в ObjectMapper.

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

Валидатор запросов Swagger от Atlassian

Swagger от Atlassian представляет собой библиотеку для валидации запросов и ответов Swagger/OpenAPI 3.0. Старая версия библиотеки не использует автоконфигурацию Spring Boot Jackson и не регистрирует JavaTimeModule в своем ObjectMapper.

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

Вместо вывода

В общем, это наиболее заметные ошибки, с которыми я столкнулся при переходе на Java 17 и которые мне показались достойными внимания.

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

Поэтому не бойтесь мигрировать свои проекты из Java 11 на Java 17. Это не так страшно, как может сначала казаться.

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

Курс-професія "Unreal Engine Developer" від robot_dreams.
Отримайте фундаментальні знання з розробки ігор, навчіться кодити на С++ та використовувати Blueprints і Gameplay Ability System, щоб створювати віртуальні всесвіти на топовому рівні.
Про курс

Этот материал – не редакционный, это – личное мнение его автора. Редакция может не разделять это мнение.

Ваша жалоба отправлена модератору

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: