Создание Fat JAR в Maven: методы сборки Java-приложения с зависимостями
Для кого эта статья:
- 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:
<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:
<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 можно детально настроить структуру архива:
<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-плагина выглядит так:
<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:
<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 становится критически большим. 🧩
Базовая конфигурация для копирования зависимостей в директорию выглядит так:
<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:
<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 помогут выявить неиспользуемые или транзитивные зависимости, что полезно для оптимизации размера результирующего приложения.
Для создания дистрибутива приложения часто используют комбинацию плагинов:
<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:
<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 могут возникать проблемы с верификацией подписей. Необходимо исключить файлы подписей из итогового архива:
<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. Для корректной работы нужно правильно обрабатывать такие файлы:
<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:
<configuration>
<minimizeJar>true</minimizeJar>
<filters>
<filter>
<artifact>commons-logging:commons-logging</artifact>
<includes>
<include>**</include>
</includes>
</filter>
</filters>
</configuration>
5. Управление версиями и именами файлов
Для удобства использования итогового JAR можно настроить его имя:
<build>
<finalName>${project.artifactId}-${project.version}-executable</finalName>
</build>
Или при использовании shade-plugin:
<configuration>
<finalName>${project.artifactId}-${project.version}-shaded</finalName>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>executable</shadedClassifierName>
</configuration>
6. Создание запускаемых скриптов
Для удобства запуска приложения можно создать скрипты (bat/sh) с помощью maven-resources-plugin:
<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.