5

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?

arghtype
  • 4,376
  • 11
  • 45
  • 60
g3blv
  • 3,877
  • 7
  • 38
  • 51
  • I have the same version of spring-boot with the same POM configuration (except: requiresUnpack is not needed). For me /BOOT-INF/ dir is not created in jar but its works. Can you check the .jar file datecreated first and next to open it with gzip and view structure? – sytolk Jan 18 '17 at 10:56

3 Answers3

2

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.

ootero
  • 3,235
  • 2
  • 16
  • 22
  • Which version of Spring Boot are you using in these examples? – g3blv Jan 18 '17 at 13:43
  • @g3blv The tutorials involving `Jersey` 2 use `Spring Boot` 1.3.5.RELEASE and 1.3.6.RELEASE. – ootero Jan 18 '17 at 15:19
  • I think your blog posts work because you are registering classes one by one. Not use `packages("some.package.to.scan")` method of the `ResourceConfig` class as @mihu86 posted below. – Yang Liu Mar 19 '22 at 02:44
2

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.)

mihu86
  • 969
  • 8
  • 9
1

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>
sytolk
  • 7,223
  • 3
  • 25
  • 38
  • 1
    My understanding is that the workaround to the issue in Spring Boot 1.4 is to use `requiresUnpack` as described just below (https://github.com/spring-projects/spring-boot/issues/1468#issuecomment-270565789) the comment you are referencing to. – g3blv Jan 18 '17 at 13:40
  • `requiresUnpack` is fix for different kind of exception like this: https://github.com/spring-projects/spring-boot/issues/1345 -> lib/jersey-client-1.11.jar (No such file or directory). But you have /BOOT-INF/classes (No such file or directory). This can be fixed from spring-boot-maven-plugin (downgrade to 1.3.8) I have the same exception like you and in @ootero example 1.3.8 version of plugin is used. This fix issue for me. – sytolk Jan 19 '17 at 08:41