g3blv
g3blv

Reputation: 4377

Spring Boot JAR built with Maven using requiresUnpack not working

I think I have been running into the following issue Jersey doesn't always work with Spring Boot fat jars. The workaround should be to set the Jersey dependencies in the POM to requiresUnpack.

My POM looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<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>net.hagstrom</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jersey</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <requiresUnpack>
                        <dependency>
                            <groupId>org.glassfish.jersey.containers</groupId>
                            <artifactId>jersey-container-servlet</artifactId>
                        </dependency>
                        <dependency>
                            <groupId>org.glassfish.jersey.core</groupId>
                            <artifactId>jersey-client</artifactId>
                        </dependency>
                    </requiresUnpack>
                </configuration>
                <version>1.4.3.RELEASE</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

But I still get the following error when I try to run the JAR that I built with mvn package:

2017-01-13 10:44:28.229 ERROR 9289 --- [ost-startStop-1] o.s.b.c.embedded.tomcat.TomcatStarter    : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration': Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jerseyConfig' defined in URL [jar:file:/home/mikael/Dev/Java/Java%20Programs/springBootDemo/target/demo-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/net/hagstrom/JerseyConfig.class]: Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [net.hagstrom.JerseyConfig]: Constructor threw exception; nested exception is org.glassfish.jersey.server.internal.scanning.ResourceFinderException: java.io.FileNotFoundException: /home/mikael/Dev/Java/Java Programs/springBootDemo/target/demo-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes (No such file or directory)

Running the JAR that I built in IDE with Artifacts works just fine.

Is there something wrong in my POM or the way I build the JAR with Maven?

Upvotes: 5

Views: 5233

Answers (3)

mihu86
mihu86

Reputation: 1005

The problem is that Jersey cannot scan classes in the new "fat boot jar". This occurs when you try to use the packages("some.package.to.scan") method of the ResourceConfig class.

However, you can achive the same effect using Spring classpath scanning facilities. This way you can scan a package similarily to config.packages():

ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
scanner.addIncludeFilter(new AnnotationTypeFilter(Path.class));
config.registerClasses(scanner.findCandidateComponents("your.package.to.scan").stream()
            .map(beanDefinition -> ClassUtils.resolveClassName(beanDefinition.getBeanClassName(), config.getClassLoader()))
            .collect(Collectors.toSet()));

Note: please have a look at the source of org.glassfish.jersey.server.internal.scanning.AnnotationAcceptingListener. This is the stock solution and you can see that it does the same: it scans for classes annotated with @Path or @Provider (but doesn't manage to find anything because of the broken scanning mechanism).

(Using the older version of the boot plugin worked for me too, but I tried to avoid it.)

Upvotes: 2

sytolk
sytolk

Reputation: 7383

I found the issue.

Spring Boot 1.4 changed the internal Jar Structure to facilitate the Spring Boot bootstrap process.

https://github.com/spring-projects/spring-boot/issues/1468#issuecomment-267357809

You can leave this version 1.4.3:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

But spring-boot-jersey fat-jar to be executable without errors it`s need to downgrade plugin version to 1.3.8 like this:

     <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>1.3.8.RELEASE</version>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

Upvotes: 1

ootero
ootero

Reputation: 3475

This might have been fixed already. I have published a couple of blog posts related to Creating APIs using Spring Boot, Jersey 2 and Docker and documenting them using Swagger available at: http://tech.asimio.net/2016/04/05/Microservices-using-Spring-Boot-Jersey-Swagger-and-Docker.html and http://tech.asimio.net/2016/05/07/Documenting-multiple-REST-API-versions-using-Spring-Boot-Jersey-and-Swagger.html, both with accompanying source code and I didn't need to unpack and repackage Jersey 2 dependencies.

On the other hand, when I was working on the accompanying source code for another blog about Services Registration and Discovery using Spring Cloud, Eureka, Ribbon and Feign, I was integrating Spring Boot and Jersey 1 (Jersey 1 doesn't have a Spring Boot starter I do recall to work-around unpackaging Jersey 1 dependencies a needed to create a multi-module Maven project for that specific API service.

Upvotes: 2

Related Questions