Создание исполняемого JAR в Maven: включение всех зависимостей
Для кого эта статья:
- Java-разработчики, желающие улучшить навыки работы с Maven и сборкой приложений
- Специалисты по DevOps, заинтересованные в упрощении процесса деплоя Java-приложений
Студенты и новички в области программирования, стремящиеся освоить создание самодостаточных JAR-файлов
Знакомы ли вам ситуации, когда приложение прекрасно работает на вашей машине, но стоит только передать его коллегам или развернуть на другом сервере, как начинаются бесконечные "А у меня этой библиотеки нет" и "ClassNotFoundException"? 🤦♂️ Создание исполняемого JAR-файла со всеми зависимостями — это настоящий must-have для Java-разработчика. Это как швейцарский нож в вашем техническом арсенале, который избавляет от головной боли при распространении приложений и гарантирует, что ваш код будет работать везде одинаково. Давайте разберемся, как превратить многофайловый кошмар в элегантный, самодостаточный JAR с помощью Maven.
Хотите освоить Maven и другие инструменты профессиональной Java-разработки? На Курсе Java-разработки от Skypro вы не только изучите теорию, но и создадите реальные проекты с использованием промышленных стандартов. Наши выпускники умеют собирать сложные приложения одним файлом и разворачивать их в любой среде. Присоединяйтесь к сообществу разработчиков, которые решают реальные задачи, а не просто пишут "Hello World". 🚀
Основы создания JAR-файлов с зависимостями в Maven
JAR-файл (Java ARchive) — стандартный формат для упаковки Java-приложений. По умолчанию Maven собирает JAR, содержащий только ваши классы, но не включает зависимости. Это создает очевидную проблему: такой JAR не будет работать самостоятельно.
Существует несколько типов JAR-файлов, которые решают эту проблему:
- Fat/Uber JAR — содержит все зависимости, распакованные в корне архива
- Shaded JAR — включает переименованные (для избежания конфликтов) зависимости
- JAR с манифестом — содержит ссылки на внешние зависимости в манифесте
- JAR с зависимостями — содержит оригинальный JAR и все зависимости в отдельных директориях
Для создания исполняемого JAR необходимо решить две ключевые задачи:
- Включить все зависимости в один файл
- Указать главный класс для запуска (точку входа)
Maven предлагает несколько плагинов для решения этих задач. Наиболее популярные:
| Плагин | Тип JAR | Особенности | Сложность настройки |
|---|---|---|---|
| maven-shade-plugin | Shaded JAR | Переименование пакетов, трансформации классов | Средняя |
| maven-assembly-plugin | JAR с зависимостями | Гибкие архивные форматы | Низкая |
| maven-jar-plugin + maven-dependency-plugin | JAR с манифестом | Зависимости отдельно | Высокая |
| spring-boot-maven-plugin | Executable JAR | Специально для Spring Boot | Низкая |
Алексей Громов, DevOps-инженер В одном из наших проектов мы столкнулись с настоящим кошмаром при деплое Java-приложения. Каждый раз при обновлении приходилось следить за десятками библиотек, часто возникали конфликты версий и неработающие компоненты. После многочасовой отладки очередного ночного деплоя я решил, что пора положить этому конец. Я интегрировал maven-shade-plugin в нашу сборку и настроил создание единого JAR-файла. Поначалу было несколько проблем с конфликтами имён, но после правильной настройки трансформеров всё встало на места. Теперь наш CI/CD пайплайн создаёт один самодостаточный JAR, который мы просто копируем на сервер и запускаем. Время деплоя сократилось с 40-50 минут до 3-5, и что еще важнее — мы забыли о бессонных ночах из-за неработающих зависимостей. Один файл — одна команда для запуска, и это работает везде.

Настройка pom.xml для упаковки всех зависимостей
Центральный файл любого Maven-проекта — pom.xml. Именно в нём мы определяем, как собирать наше приложение. Для создания JAR с зависимостями необходимо правильно настроить секцию <build> и добавить соответствующие плагины. 🛠️
Базовая структура pom.xml должна содержать следующие ключевые элементы:
- Координаты проекта (groupId, artifactId, version)
- Зависимости (dependencies)
- Конфигурация сборки (build)
- Настройки плагинов (plugins)
Рассмотрим пример базового pom.xml файла:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Здесь будут настройки плагинов для сборки JAR -->
</plugins>
</build>
</project>
Для успешной сборки JAR с зависимостями важно учитывать следующие аспекты:
| Элемент pom.xml | На что влияет | Рекомендации |
|---|---|---|
<dependencies> | Набор библиотек в JAR | Указывайте точные версии, избегайте конфликтующих зависимостей |
<scope> у зависимостей | Включение в финальный JAR | 'provided' и 'test' не попадут в JAR |
<maven.compiler.source/target> | Совместимость с Java | Выбирайте версию исходя из требований к среде запуска |
<mainClass> | Точка входа в приложение | Обязательна для исполняемого JAR |
Чтобы Maven знал, какой класс должен быть запущен при выполнении JAR-файла, необходимо указать mainClass. Это делается в конфигурации соответствующего плагина. Также важно убедиться, что этот класс имеет метод public static void main(String[] args).
Maven-shade-plugin: конфигурация для исполняемого JAR
Maven Shade Plugin — один из самых мощных и гибких инструментов для создания исполняемых JAR-файлов. Он не просто копирует зависимости в архив, а может выполнять сложные трансформации, включая переименование пакетов для предотвращения конфликтов. 🔄
Добавление плагина в pom.xml выглядит следующим образом:
<build>
<plugins>
<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.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Ключевые элементы конфигурации:
<phase>package</phase>— указывает, что плагин выполняется на фазе package жизненного цикла Maven<mainClass>— полное имя класса с методом main<transformers>— определяют как обрабатываются ресурсы при объединении
Shade плагин предоставляет ряд дополнительных опций для тонкой настройки:
<configuration>
<!-- Создаёт минимизированную версию JAR -->
<minimizeJar>true</minimizeJar>
<!-- Фильтры для включения/исключения файлов -->
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<!-- Изменяет имя выходного файла -->
<finalName>uber-${project.artifactId}-${project.version}</finalName>
<!-- Прикрепляет классификатор к имени артефакта -->
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>with-dependencies</shadedClassifierName>
<!-- Трансформеры для обработки ресурсов -->
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.Main</mainClass>
</transformer>
<!-- Объединяет файлы services/ -->
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
</configuration>
Для проектов с большим количеством зависимостей могут возникать конфликты из-за дублирующихся файлов. Shade Plugin позволяет решать это с помощью переименования пакетов:
<relocations>
<relocation>
<pattern>org.apache.commons</pattern>
<shadedPattern>shaded.org.apache.commons</shadedPattern>
</relocation>
</relocations>
После настройки плагина сборка производится стандартной командой:
mvn clean package
Maven создаст два JAR-файла в директории target:
- my-app-1.0-SNAPSHOT.jar — стандартный JAR без зависимостей
- uber-my-app-1.0-SNAPSHOT.jar (или как указано в
<finalName>) — исполняемый JAR со всеми зависимостями
Альтернативные плагины для сборки JAR с зависимостями
Maven-shade-plugin — не единственный способ создать исполняемый JAR. Существуют альтернативные плагины с разными преимуществами и ограничениями. Выбор зависит от конкретных требований проекта. 🧩
Максим Петров, Lead Java Developer На моем предыдущем проекте мы использовали maven-assembly-plugin для упаковки микросервисов. Всё работало отлично, пока мы не начали интегрировать сервисы с технологиями, использующими Service Provider Interface (SPI), такими как JDBC-драйверы и логгеры. Неожиданно приложение стало терять некоторые драйверы и другие SPI-компоненты. Оказалось, что assembly-plugin просто копирует файлы, не объединяя содержимое одноименных файлов в директориях META-INF/services, критичных для SPI. Мы перешли на maven-shade-plugin с настроенным ServicesResourceTransformer. Это потребовало переписывания части конфигурации сборки, но полностью решило проблему — все SPI-компоненты стали корректно регистрироваться и работать. Дополнительным бонусом стала возможность переименования потенциально конфликтующих пакетов с помощью relocations. Этот случай научил меня тщательнее выбирать инструменты сборки исходя из всех аспектов приложения, а не только базовой функциональности.
Рассмотрим основные альтернативы maven-shade-plugin:
1. Maven Assembly Plugin
Этот плагин хорошо подходит для простых случаев и позволяет создавать различные типы дистрибутивов.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.example.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
2. Maven JAR Plugin + Dependency Plugin
Комбинация этих плагинов создаёт JAR с манифестом, ссылающимся на отдельные зависимости. Это увеличивает размер дистрибутива, но может быть полезно при частом обновлении только основного кода.
<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.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<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>
</configuration>
</execution>
</executions>
</plugin>
3. Spring Boot Maven Plugin
Специализированный плагин для Spring Boot приложений. Автоматически создаёт исполняемый JAR с корректной структурой для загрузчика Spring Boot.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.9</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
Сравнение плагинов по ключевым параметрам:
| Функция / Плагин | shade | assembly | jar + dependency | spring-boot |
|---|---|---|---|---|
| Объединение SPI файлов | ✅ | ❌ | ❌ | ✅ |
| Переименование пакетов | ✅ | ❌ | ❌ | ❌ |
| Минимизация JAR | ✅ | ❌ | ❌ | ❌ |
| Один файл | ✅ | ✅ | ❌ | ✅ |
| Настраиваемые дистрибутивы | ⚠️ | ✅ | ⚠️ | ⚠️ |
При выборе плагина учитывайте:
- Сложность проекта и его зависимостей
- Наличие SPI-компонентов (JDBC, логирование и т.д.)
- Потенциальные конфликты зависимостей
- Требования к размеру итогового JAR
- Требования к дистрибутиву (один файл или архив с вспомогательными файлами)
Тестирование и запуск собранного JAR-файла
После успешной сборки JAR-файла критически важно протестировать его работоспособность, особенно в среде, отличной от вашей разработческой машины. Правильно собранный JAR должен запускаться простой командой и работать идентично на любой системе с установленной Java. 🚀
Основные шаги тестирования JAR-файла:
- Запуск в другой директории — убедитесь, что JAR не зависит от локальных путей
- Проверка на чистой машине — без установленных зависимостей
- Тестирование с разными версиями Java — для выявления проблем совместимости
- Проверка логов на ошибки — некоторые проблемы могут быть не очевидны
Запуск JAR-файла осуществляется командой:
java -jar target/your-app-with-dependencies.jar
Для более тонкой настройки запуска можно использовать JVM-опции:
java -Xms512m -Xmx1024m -jar target/your-app-with-dependencies.jar
Для приложений с расширенной конфигурацией можно передавать аргументы командной строки:
java -jar target/your-app-with-dependencies.jar --config=prod.properties
Часто возникающие проблемы и их решения:
- ClassNotFoundException — не включена нужная зависимость, проверьте scope в pom.xml
- NoClassDefFoundError — конфликт версий или неправильно настроен плагин сборки
- Отсутствие ресурсов — проверьте, что ресурсы правильно включены в JAR
- "no main manifest attribute" — не указан mainClass в конфигурации плагина
- UnsatisfiedLinkError — проблемы с нативными библиотеками, которые должны быть доступны в системе
Для проверки содержимого JAR-файла можно использовать команды:
jar tf target/your-app-with-dependencies.jar
Или для просмотра манифеста:
jar xf target/your-app-with-dependencies.jar META-INF/MANIFEST.MF
cat META-INF/MANIFEST.MF
Для автоматизации проверки JAR-файла создайте скрипт тестирования, который:
- Запускает JAR в чистой директории
- Выполняет базовый набор операций
- Проверяет ожидаемые результаты
- Отлавливает ошибки и исключения
Пример простого bash-скрипта для тестирования:
#!/bin/bash
# Создаем временную директорию
TEST_DIR=$(mktemp -d)
cp target/your-app-with-dependencies.jar $TEST_DIR/
cd $TEST_DIR
# Запускаем с тестовыми параметрами и перенаправляем вывод
java -jar your-app-with-dependencies.jar --test > output.log 2>&1
# Проверяем результат
if grep -q "Successfully completed" output.log; then
echo "Test passed! ✓"
exit 0
else
echo "Test failed! ✗"
cat output.log
exit 1
fi
Помните, что хорошая практика DevOps — включать подобные тесты в ваш CI/CD-пайплайн, чтобы автоматически проверять каждую новую версию JAR-файла перед релизом.
Создание исполняемого JAR с зависимостями — это больше, чем просто техническое решение. Это философия упрощения деплоя и распространения приложений. Когда у вас есть один самодостаточный файл, исчезают проблемы с несовместимостью версий зависимостей, упрощается автоматизация и развертывание. Будь то микросервис в корпоративной инфраструктуре или инструмент с открытым исходным кодом — правильно упакованный JAR превращается из головной боли в инструмент, которым можно гордиться. Применяйте описанные методы и пусть ваши Java-приложения работают везде так же хорошо, как и на вашей машине. 🚀