Создание Fat JAR в Maven: методы сборки Java-приложения с зависимостями

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Java-разработчики, которые хотят улучшить свои навыки в упаковке приложений
  • Студенты и начинающие программисты, обучающиеся на курсах Java-разработки
  • Профессионалы, работающие с Maven и заинтересованные в оптимизации процесса деплоя Java-приложений

    Каждый Java-разработчик рано или поздно сталкивается с задачей: сделать так, чтобы приложение запускалось одной командой, без мучительного подключения библиотек. Именно здесь на сцену выходит fat JAR — элегантное решение для упаковки приложения со всеми зависимостями в один исполняемый файл. Если вы устали от ошибок ClassNotFoundException или хотите упростить процесс деплоя, это руководство раскроет все тонкости создания автономных JAR-файлов с использованием Maven — от базовой концепции до продвинутых техник трансформации классов. 📦✨

Погружаясь в мир Maven и управления зависимостями, вы делаете важный шаг к профессиональной Java-разработке. На Курсе Java-разработки от Skypro вы не только освоите создание fat JAR, но и получите комплексные знания по всему стеку — от Core Java до Spring и инструментов автоматизации. Наши студенты решают реальные задачи под руководством практикующих экспертов, создавая полноценные проекты для портфолио.

Что такое Fat JAR и зачем его создавать в Maven

Fat JAR (также известный как uber JAR или shaded JAR) — это JAR-файл, который содержит не только классы вашего приложения, но и все необходимые зависимости. Фактически, это автономный исполняемый архив, который можно запустить без необходимости настраивать classpath или устанавливать дополнительные библиотеки. 🚀

Стандартный JAR, создаваемый Maven по умолчанию, содержит только ваши классы и ресурсы, но не включает зависимости. Это создаёт типичную проблему: при попытке запустить такой JAR вы получаете неприятное сообщение об ошибке ClassNotFoundException или NoClassDefFoundError.

Артём Викторов, Tech Lead Java-проектов

Однажды наша команда столкнулась с ситуацией, когда микросервис отлично работал на dev-окружении, но отказывался запускаться в production. После часа отладки выяснилось, что системный администратор запускал обычный JAR без указания classpath, и критически важные библиотеки не загружались. Решение было простым — мы сконфигурировали maven-assembly-plugin, создали fat JAR и больше никогда не сталкивались с подобными проблемами. Более того, процесс деплоя упростился настолько, что теперь мы могли обновлять приложение одной командой.

Преимущества fat JAR очевидны:

  • Простота развёртывания — один файл содержит всё необходимое
  • Отсутствие проблем с версиями зависимостей — они "заморожены" внутри JAR
  • Снижение ошибок при развёртывании в разных окружениях
  • Упрощение настройки Docker-контейнеров
  • Возможность запуска приложения без установки Maven

Однако есть и недостатки, о которых стоит помнить:

Недостаток Описание Возможное решение
Увеличение размера Fat JAR может быть значительно больше обычного Исключение ненужных зависимостей
Конфликты ресурсов Файлы с одинаковыми путями из разных библиотек перезаписывают друг друга Использование maven-shade-plugin с трансформацией ресурсов
Проблемы с лицензированием Не все лицензии разрешают переупаковку библиотек Проверка лицензий зависимостей
Сложности с обновлением Для обновления одной библиотеки нужно пересобирать весь JAR Модульная архитектура с отдельными компонентами

Maven предоставляет несколько плагинов для создания fat JAR, каждый со своими особенностями:

  • maven-assembly-plugin — простой и гибкий способ создания различных архивов
  • maven-shade-plugin — продвинутый плагин с возможностью трансформации классов и ресурсов
  • maven-dependency-plugin — предоставляет более детальный контроль над библиотеками

Выбор плагина зависит от сложности проекта и конкретных требований. Для большинства случаев подойдёт assembly или shade.

Пошаговый план для смены профессии

Maven-Assembly-Plugin: настройка для сборки JAR с зависимостями

Maven-Assembly-Plugin — это наиболее простой и понятный способ создать fat JAR. Он отлично подходит для небольших и средних проектов, где нет необходимости в сложной трансформации классов или манипуляциях с метаданными. ⚙️

Для базовой настройки достаточно добавить следующий фрагмент в секцию plugins вашего pom.xml:

xml
Скопировать код
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.example.MainClass</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

Эта настройка создаст исполняемый JAR с суффиксом -jar-with-dependencies. После выполнения команды mvn package в директории target появится файл вида myapp-1.0-jar-with-dependencies.jar.

Ключевые элементы конфигурации:

  • descriptorRef — предопределённый шаблон сборки (jar-with-dependencies включает все зависимости)
  • mainClass — класс с методом main, который будет указан в MANIFEST.MF как точка входа
  • phase — фаза жизненного цикла Maven, на которой запустится плагин
  • goal — цель плагина (single создаёт один архив)

Для более сложных сценариев можно создать собственный дескриптор сборки в формате XML:

xml
Скопировать код
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<descriptors>
<descriptor>src/assembly/distribution.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

А в файле src/assembly/distribution.xml можно детально настроить структуру архива:

xml
Скопировать код
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>distribution</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
<excludes>
<exclude>org.example:unwanted-dependency</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>

Такой подход даёт полный контроль над содержимым JAR и позволяет:

  • Исключать ненужные зависимости
  • Определять структуру каталогов внутри архива
  • Добавлять дополнительные файлы (конфигурации, лицензии, README)
  • Создавать различные форматы архивов (jar, zip, tar, war)

Assembly-plugin отлично подходит для большинства проектов, но имеет ограничение — он не решает проблему конфликтов классов с одинаковыми путями из разных библиотек. В таких случаях стоит обратить внимание на Shade-plugin.

Maven-Shade-Plugin: трансформация классов и решение конфликтов

Maven-Shade-Plugin — это мощный инструмент, который выходит далеко за рамки простого объединения JAR-файлов. Его ключевая особенность — возможность "затенять" (shading) классы, то есть переименовывать их пакеты для предотвращения конфликтов. Это особенно полезно, когда разные библиотеки используют одинаковые имена классов или когда вы хотите включить в свой JAR несколько версий одной библиотеки. 🛡️

Базовая конфигурация Shade-плагина выглядит так:

xml
Скопировать код
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.MainClass</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

В отличие от Assembly-plugin, Shade-plugin по умолчанию заменяет оригинальный JAR своей версией, а не создаёт отдельный файл с суффиксом. Если вы хотите сохранить оригинальный JAR, добавьте параметр <createDependencyReducedPom>true</createDependencyReducedPom>.

Теперь рассмотрим более сложную конфигурацию, демонстрирующую возможности Shade-plugin:

xml
Скопировать код
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>com.google</pattern>
<shadedPattern>shaded.com.google</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.MainClass</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

Давайте разберём ключевые элементы этой конфигурации:

Элемент Назначение Пример использования
filters Фильтрация файлов из зависимостей Исключение подписанных файлов для предотвращения ошибок верификации
relocations Переименование пакетов для устранения конфликтов Перенос классов Google Guava в собственное пространство имен
ServicesResourceTransformer Объединяет файлы META-INF/services/* Необходим для правильной работы SPI (Service Provider Interface)
AppendingTransformer Объединяет содержимое файлов с одинаковыми именами Слияние конфигурации Spring Framework из разных JAR-файлов

Особое внимание следует уделить механизму relocation. При сборке fat JAR часто возникает проблема: несколько библиотек могут использовать одни и те же классы, но разных версий. Например, ваше приложение использует библиотеку A, которая зависит от Guava 20.0, и библиотеку B, зависящую от Guava 30.0.

Максим Головин, Senior Java-разработчик

В одном из проектов мы столкнулись с настоящим "dependency hell". Приложение использовало библиотеки с несовместимыми версиями Apache Commons и Google Guava. При создании fat JAR через assembly-plugin приложение падало с загадочными ошибками — методы возвращали неожиданные результаты или выбрасывали NoSuchMethodError.

Решение пришло после применения maven-shade-plugin с настройкой relocations. Мы изменили пространства имен проблемных библиотек, добавив префиксы: commons-io → shaded.commons.io и com.google → internal.google. Это позволило библиотекам сосуществовать без конфликтов. Важно было также настроить MinimizeJar и добавить фильтры для исключения служебных файлов META-INF. После этих изменений приложение стало стабильно работать в любом окружении.

Shade-plugin имеет и другие полезные возможности:

  • MinimizeJar — автоматическое удаление неиспользуемых классов для уменьшения размера JAR
  • DontIncludePattern — возможность исключить определённые пакеты или классы
  • Трансформаторы ресурсов — специальные обработчики для разных типов файлов (XML, properties, etc.)
  • Шейдинг транзитивных зависимостей — возможность обработать только определённые уровни зависимостей

Для сложных проектов с множеством зависимостей Shade-plugin является предпочтительным выбором, несмотря на более сложную настройку по сравнению с Assembly-plugin.

Maven-Dependency-Plugin: гибкое управление библиотеками

Maven-Dependency-Plugin предлагает несколько иной подход к созданию исполняемых приложений. В отличие от Assembly и Shade, которые упаковывают всё в один JAR-файл, Dependency-plugin позволяет распаковать зависимости в отдельную директорию и затем запускать приложение с указанием classpath. Этот подход особенно полезен, когда требуется более гибкое управление библиотеками или когда размер fat JAR становится критически большим. 🧩

Базовая конфигурация для копирования зависимостей в директорию выглядит так:

xml
Скопировать код
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>

Этот плагин копирует все зависимости в директорию target/lib. Чтобы создать исполняемый JAR, который будет использовать эти библиотеки, нужно дополнительно настроить maven-jar-plugin:

xml
Скопировать код
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.example.MainClass</mainClass>
</manifest>
</archive>
</configuration>
</plugin>

Эта конфигурация создаст JAR-файл с манифестом, который включает ссылки на библиотеки в директории lib/. При запуске такого JAR классы будут загружаться из указанных библиотек.

Dependency-plugin особенно полезен в следующих сценариях:

  • Когда приложение слишком большое для упаковки в один JAR
  • При необходимости заменять отдельные библиотеки без пересборки всего приложения
  • Для создания модульной структуры с возможностью добавления плагинов
  • В случаях, когда нужно предоставить пользователю возможность настраивать используемые библиотеки

Кроме базовой функциональности, Dependency-plugin предлагает множество опций для тонкой настройки процесса копирования зависимостей:

  • includeScope/excludeScope — фильтрация зависимостей по области видимости (compile, runtime, test)
  • includeGroupIds/excludeGroupIds — фильтрация по группам артефактов
  • includeArtifactIds/excludeArtifactIds — фильтрация по идентификаторам артефактов
  • classifier — выбор конкретного классификатора артефакта
  • stripVersion — удаление версии из имени файла при копировании

Dependency-plugin может также использоваться для анализа зависимостей вашего проекта. Команды mvn dependency:tree и mvn dependency:analyze помогут выявить неиспользуемые или транзитивные зависимости, что полезно для оптимизации размера результирующего приложения.

Для создания дистрибутива приложения часто используют комбинацию плагинов:

xml
Скопировать код
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<descriptors>
<descriptor>src/assembly/distribution.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

И соответствующий дескриптор src/assembly/distribution.xml:

xml
Скопировать код
<assembly>
<id>distribution</id>
<formats>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>./</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/lib</directory>
<outputDirectory>lib</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>src/main/scripts</directory>
<outputDirectory>./</outputDirectory>
<includes>
<include>*.sh</include>
<include>*.bat</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
</fileSets>
</assembly>

Этот подход создаст ZIP-архив с исполняемым JAR, библиотеками в директории lib/ и скриптами запуска.

Практические советы по созданию исполняемого JAR файла

Создание правильного исполняемого JAR требует внимания к деталям и понимания специфических нюансов. Вот несколько практических советов, которые помогут избежать распространённых проблем и оптимизировать процесс сборки. 🔧

1. Выбор правильного плагина в зависимости от задачи

  • maven-assembly-plugin — для простых случаев, когда нет конфликтов классов
  • maven-shade-plugin — для сложных проектов с множеством зависимостей и возможными конфликтами
  • maven-dependency-plugin + maven-jar-plugin — для модульных приложений или когда требуется возможность замены библиотек
  • spring-boot-maven-plugin — для Spring Boot приложений (включает оптимизации специфичные для Spring Boot)

2. Решение проблемы с подписанными JAR

При включении подписанных библиотек в fat JAR могут возникать проблемы с верификацией подписей. Необходимо исключить файлы подписей из итогового архива:

xml
Скопировать код
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>

3. Обработка файлов ресурсов с одинаковыми именами

Библиотеки часто содержат файлы с одинаковыми путями, например, META-INF/services/* или конфигурационные файлы Spring. Для корректной работы нужно правильно обрабатывать такие файлы:

xml
Скопировать код
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
</transformers>

4. Оптимизация размера JAR

Для уменьшения размера итогового JAR можно:

  • Исключить ненужные зависимости через exclusions в pom.xml
  • Использовать minimizeJar в shade-plugin для удаления неиспользуемых классов
  • Применять фильтры для исключения ненужных файлов (тестовых ресурсов, документации)
  • Включать только необходимые области зависимостей (compile, runtime, но не test)

Пример настройки minimizeJar:

xml
Скопировать код
<configuration>
<minimizeJar>true</minimizeJar>
<filters>
<filter>
<artifact>commons-logging:commons-logging</artifact>
<includes>
<include>**</include>
</includes>
</filter>
</filters>
</configuration>

5. Управление версиями и именами файлов

Для удобства использования итогового JAR можно настроить его имя:

xml
Скопировать код
<build>
<finalName>${project.artifactId}-${project.version}-executable</finalName>
</build>

Или при использовании shade-plugin:

xml
Скопировать код
<configuration>
<finalName>${project.artifactId}-${project.version}-shaded</finalName>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>executable</shadedClassifierName>
</configuration>

6. Создание запускаемых скриптов

Для удобства запуска приложения можно создать скрипты (bat/sh) с помощью maven-resources-plugin:

xml
Скопировать код
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}</outputDirectory>
<resources>
<resource>
<directory>src/main/scripts</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>

7. Тестирование собранного JAR

Обязательно проверьте работоспособность итогового JAR в различных условиях:

  • Запуск через командную строку: java -jar your-app.jar
  • Запуск с дополнительными параметрами JVM: java -Xmx512m -jar your-app.jar
  • Запуск в другом окружении (особенно если отличается версия Java)
  • Тестирование в контейнерах Docker или других изолированных средах

8. Специальные случаи

Для разных типов приложений могут потребоваться специфические настройки:

  • Spring Boot: Используйте spring-boot-maven-plugin вместо стандартных плагинов
  • SWT/JavaFX: Нужно учитывать нативные библиотеки, специфичные для платформы
  • Log4j/Logback: Обратите внимание на конфигурационные файлы логирования
  • JDBC драйверы: Часто требуют регистрации через META-INF/services

Следуя этим советам, вы сможете создать надёжный исполняемый JAR-файл, который будет корректно работать в различных окружениях и упростит процесс развёртывания вашего приложения.

Создание fat JAR — это не просто техническая операция, а стратегический выбор, влияющий на весь жизненный цикл вашего приложения. Правильно настроенный процесс сборки существенно упрощает развертывание, тестирование и поддержку Java-приложений. Выбор между различными плагинами Maven должен основываться на специфике вашего проекта: assembly для простых случаев, shade для сложных зависимостей с потенциальными конфликтами, или dependency для модульных систем. Помните, что хороший fat JAR — это не просто все классы в одном архиве, а тщательно продуманная структура, учитывающая особенности загрузки классов и управления ресурсами в Java.

Загрузка...