«Ваше приложение работает только в IDE? Пора это исправить!»
Если вы когда-нибудь писали на Java, то наверняка сталкивались с ситуацией: вы гордо показываете другу свой новый проект, запускаете его в IntelliJ IDEA или Eclipse — всё работает идеально. Но стоит передать этот проект коллеге или попробовать запустить его через командную строку — и вас встречает ClassNotFoundException или NoClassDefFoundError. Знакомо?
В большинстве учебных курсов и туториалов показывают, как создать JAR файл с помощью Maven, но часто упускают одну важную деталь: куда деваются зависимости?. Обычный JAR-файл содержит только ваш код, а все библиотеки (например, Apache Commons, Jackson, JUnit) остаются за бортом. Именно поэтому возникает вопрос, который задают миллионы Java-разработчиков на Stack Overflow: «How can I create an executable/runnable JAR with dependencies using Maven?»
В этой статье мы разберём несколько проверенных способов собрать исполняемый JAR с зависимостями с помощью Maven. Вы узнаете, как настроить maven-assembly-plugin, maven-shade-plugin и даже как это работает в Spring Boot. Поехали!
Почему обычный JAR не работает?
Давайте проведём простой эксперимент. Представьте, что у вас есть приложение, которое парсит JSON с помощью библиотеки Jackson. Вы создаёте обычный JAR командой mvn package, а затем пытаетесь запустить его:
java -jar my-app.jar
И получаете что-то вроде:
Exception in thread "main" java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper
Почему так происходит? Потому что Maven по умолчанию упаковывает только ваш скомпилированный код (классы из папки src/main/java). Все зависимости, которые вы указали в pom.xml, остаются в локальном репозитории Maven (~/.m2/repository). Когда вы запускаете JAR, JVM не знает, где искать эти библиотеки, и выдаёт ошибку.
Решение — создать fat JAR (толстый JAR) или uber JAR, внутри которого будут не только ваши классы, но и все необходимые зависимости. Именно для этого существуют специальные плагины Maven.
Способ 1: maven-assembly-plugin (просто и надёжно)
Самый популярный и простой способ — использовать maven-assembly-plugin. Он создаёт JAR, в котором все зависимости распакованы и объединены с вашими классами. Давайте настроим его.
Добавьте в ваш pom.xml следующий код в раздел <build><plugins>:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.example.Main</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
Важно: замените com.example.Main на полное имя вашего класса с методом main.
Теперь соберите проект командой:
mvn clean package
В папке target появятся два файла:
my-app.jar— обычный JAR без зависимостейmy-app-jar-with-dependencies.jar— наш толстый JAR
Запускаем:
java -jar target/my-app-jar-with-dependencies.jar
Готово! Приложение должно запуститься без ошибок.
Способ 2: maven-shade-plugin (продвинутый, с возможностью переименовывать классы)
Второй популярный вариант — maven-shade-plugin. Он не только объединяет зависимости, но и может решать конфликты версий (если две библиотеки используют один и тот же класс). Особенно это полезно, если вы используете библиотеки, которые сами тянут за собой другие зависимости.
Настройка почти такая же:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.Main</mainClass>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
Собираем:
mvn clean package
Результат — target/my-app.jar (shade-плагин заменяет оригинальный JAR). Обратите внимание: плагин создаёт также файл dependency-reduced-pom.xml, в котором зависимости уже не указаны, так как они внутри JAR.
Когда выбирать shade? Если у вас сложный проект с десятками зависимостей, и вы боитесь конфликтов классов (например, Log4j разных версий). Shade умеет «перепаковывать» классы, меняя их пакеты, чтобы избежать коллизий.
Способ 3: Spring Boot Maven Plugin (если вы используете Spring)
Если вы пишете на Spring Boot, то, скорее всего, уже используете spring-boot-maven-plugin. Он автоматически создаёт исполняемый JAR со всеми зависимостями. Ничего дополнительно настраивать не нужно!
Вот минимальная конфигурация pom.xml для Spring Boot приложения:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<build>
<plugins>
<plugin>
<groupId