spring-boot jersey based application : can not start without errors using java -jar command

Created on 7 Aug 2014  Â·  20Comments  Â·  Source: spring-projects/spring-boot

It works fine if I run it using:

OK: mvn spring-boot:run

but not relying on the spring-boot maven plugin and with a plain:

NOK : java -jar target/spring-boot-jersey-test-0.0.1-SNAPSHOT.jar

For the 2nd case, App starts but the following errors are visible during startup

[2014-08-07 12:54:47.531] boot - 7632 ERROR [localhost-startStop-1] --- [/]: Exception starting filter servletContainer
com.sun.jersey.core.spi.scanning.ScannerException: IO error when scanning jar jar:file:/Users/boostrack/spring-boot-jersey-test/target/spring-boot-jersey-test-0.0.1-SNAPSHOT.jar!/lib/jersey-client-1.11.jar!/com/sun/jersey
    at com.sun.jersey.core.spi.scanning.uri.JarZipSchemeScanner.scan(JarZipSchemeScanner.java:82)
    at com.sun.jersey.core.spi.scanning.PackageNamesScanner.scan(PackageNamesScanner.java:223)
    at com.sun.jersey.core.spi.scanning.PackageNamesScanner.scan(PackageNamesScanner.java:139)
    at com.sun.jersey.api.core.ScanningResourceConfig.init(ScanningResourceConfig.java:80)
    at com.sun.jersey.api.core.PackagesResourceConfig.init(PackagesResourceConfig.java:104)
    at com.sun.jersey.api.core.PackagesResourceConfig.<init>(PackagesResourceConfig.java:78)
    at com.sun.jersey.api.core.PackagesResourceConfig.<init>(PackagesResourceConfig.java:89)
    at com.sun.jersey.spi.container.servlet.WebComponent.createResourceConfig(WebComponent.java:700)
    at com.sun.jersey.spi.container.servlet.WebComponent.createResourceConfig(WebComponent.java:678)
    at com.sun.jersey.spi.container.servlet.WebComponent.init(WebComponent.java:203)
    at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:374)
    at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:727)
    at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:279)
    at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:109)
    at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4809)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5485)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.FileNotFoundException: /Users/boostrack/spring-boot-jersey-test-0.0.1-SNAPSHOT.jar!/lib/jersey-client-1.11.jar (No such file or directory)
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:131)
    at java.io.FileInputStream.<init>(FileInputStream.java:87)
    at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:90)
    at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:188)
    at java.net.URL.openStream(URL.java:1038)
    at com.sun.jersey.core.spi.scanning.uri.JarZipSchemeScanner.closing(JarZipSchemeScanner.java:123)
    at com.sun.jersey.core.spi.scanning.uri.JarZipSchemeScanner.scan(JarZipSchemeScanner.java:75)
    ... 22 more
[2014-08-07 12:54:47.558] boot - 7632 ERROR [localhost-startStop-1] --- StandardContext: Error filterStart
[2014-08-07 12:54:47.560] boot - 7632 ERROR [localhost-startStop-1] --- StandardContext: Context [] startup failed due to previous errors

and the application does not respond as expected.

PS: did not tested how it works with gradle plugin

invalid

Most helpful comment

I use the following code (that works as spring beans)
jersey 1:

import java.util.Map;

import javax.annotation.PostConstruct;
import javax.ws.rs.Path;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import com.sun.jersey.api.core.DefaultResourceConfig;

@Component
public class RestApplication extends DefaultResourceConfig {

    private static final Logger log = LoggerFactory.getLogger(RestApplication.class);

    public RestApplication() {
        getFeatures().put("com.sun.jersey.api.json.POJOMappingFeature", true);
    }

    @Autowired
    ApplicationContext appCtx;

    @PostConstruct
    public void setup() {
        log.info("Rest classes found:");
        Map<String,Object> beans = appCtx.getBeansWithAnnotation(Path.class);
        for (Object o : beans.values()) {
            log.info(" -> " + o.getClass().getName());
            getSingletons().add(o);
        }
    }

}

and

    @Autowired
    RestApplication restApplication;

    @Bean
    public ServletRegistrationBean jersey() {
        ServletRegistrationBean bean = new ServletRegistrationBean();
        bean.setServlet(new ServletContainer(restApplication));
        bean.addInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
        bean.setUrlMappings(Arrays.asList("/rest/*"));
        return bean;
    }

jersery2:

import java.util.Map;

import javax.annotation.PostConstruct;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.ResourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import com.sun.jersey.api.core.ResourceConfig;

@Component
@ApplicationPath("/rest")
public class JerseyConfig extends ResourceConfig {

    private static final Logger log = LoggerFactory.getLogger(JerseyConfig.class);

    @Autowired
    ApplicationContext appCtx;

    @PostConstruct
    public void setup() {
        log.info("Rest classes found:");
        Map<String,Object> beans = appCtx.getBeansWithAnnotation(Path.class);
        for (Object o : beans.values()) {
            log.info(" -> " + o.getClass().getName());
            register(o);
        }
    }

}

All 20 comments

In the future please ask this sort of question on StackOverflow using the spring-boot tag.

It looks like you've configured Jersey to scan the com.sun.jersey package. The jars containing that package are nested within your application's jar file and Jersey appears to be unable to scan a nested jar.

If you need Jersey to scan the com.sun.jersey package then you can configure Boot's Maven plugin to unpack the jars containing that package:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <requiresUnpack>
                <dependency>
                        <groupId>com.sun.jersey</groupId>
                        <artifactId>jersey-server</artifactId>
                </dependency>
                <dependency>
                        <groupId>com.sun.jersey</groupId>
                        <artifactId>jersey-core</artifactId>
                </dependency>
                <dependency>
                        <groupId>com.sun.jersey</groupId>
                        <artifactId>jersey-servlet</artifactId>
                </dependency>
        </requiresUnpack>
    </configuration>
</plugin>

See the documentation for more details.

With 1.2.0.M2 I can't seem to get the same effect when using the following with Gradle:

apply plugin: 'spring-boot'

springBoot {
  requiresUnpack = [ "com.google.guava:guava" ]
}

It simply doesn't unpack in the resulting jar.

Actually, I can't see where requiresUnpack is consumed in Spring Boot:

https://github.com/spring-projects/spring-boot/search?utf8=%E2%9C%93&q=requiresUnpack

From http://docs.spring.io/autorepo/docs/spring-boot/1.2.0.M2/reference/html/build-tool-plugins-gradle-plugin.html#build-tool-plugins-gradle-configuration-options

requiresUnpack A list of dependencies (in the form ``groupId:artifactId'' that must be unpacked from fat jars in order to run. Items are still packaged into the fat jar, but they will be automatically unpacked when it runs.

I'm not exactly sure how to interpret that actually. Is "it runs" referring to the execution repackaged jar or the execution of bootRepackage task?

@btiernay That search shows the two places where requiresUnpack is consumed. One in the Maven plugin and one in the Gradle plugin.

It simply doesn't unpack in the resulting jar

That's not the intention of requiresUnpack. Any dependency marked with requiresUnpack will be unpacked at runtime. This is explained in the documentation:

Most nested libraries in an executable jar do not need to be unpacked in order to run, however, certain libraries can have problems. For example, JRuby includes its own nested jar support which assumes that the jruby-complete.jar is always directly available as a file in its own right.

To deal with any problematic libraries, you can flag that specific nested jars should be automatically unpacked to the ‘temp folder’ when the executable jar first runs.

@wilkinsona Thanks for the explanation. I guess I must have missed that section in the docs.

With regard to the Gradle plugin, I still don't see where requiresUnpack is actually used. I would have thought it would have been consumed in https://github.com/spring-projects/spring-boot/blob/f97251b9cf80aa5cda21340d6cbcd28852db5c14/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/repackage/RepackageTask.java.

It's here

@wilkinsona thanks for your reply, but it's still not working in my case.
I am unpack some jar from my company and jersey lib.

``` <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <requiresUnpack> <dependency> <groupId>com.services</groupId> <artifactId>customer-services</artifactId> <version>0.0.3-SNAPSHOT</version> </dependency> <dependency> <groupId>com.services</groupId> <artifactId>program-services</artifactId> <version>0.0.3-SNAPSHOT</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-server</artifactId> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-core</artifactId> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-servlet</artifactId> </dependency> </requiresUnpack> </configuration> </plugin> </plugins> </build>

``````

@wuqunfei This is issue is closed. If you believe you'd found a bug or limitation, please open a new issue providing a sample project that reproduces the problem.

@wilkinsona thanks
I will open this issure again, but I will prepare a simple structure of my case at first.

I use the following code (that works as spring beans)
jersey 1:

import java.util.Map;

import javax.annotation.PostConstruct;
import javax.ws.rs.Path;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import com.sun.jersey.api.core.DefaultResourceConfig;

@Component
public class RestApplication extends DefaultResourceConfig {

    private static final Logger log = LoggerFactory.getLogger(RestApplication.class);

    public RestApplication() {
        getFeatures().put("com.sun.jersey.api.json.POJOMappingFeature", true);
    }

    @Autowired
    ApplicationContext appCtx;

    @PostConstruct
    public void setup() {
        log.info("Rest classes found:");
        Map<String,Object> beans = appCtx.getBeansWithAnnotation(Path.class);
        for (Object o : beans.values()) {
            log.info(" -> " + o.getClass().getName());
            getSingletons().add(o);
        }
    }

}

and

    @Autowired
    RestApplication restApplication;

    @Bean
    public ServletRegistrationBean jersey() {
        ServletRegistrationBean bean = new ServletRegistrationBean();
        bean.setServlet(new ServletContainer(restApplication));
        bean.addInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
        bean.setUrlMappings(Arrays.asList("/rest/*"));
        return bean;
    }

jersery2:

import java.util.Map;

import javax.annotation.PostConstruct;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.ResourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import com.sun.jersey.api.core.ResourceConfig;

@Component
@ApplicationPath("/rest")
public class JerseyConfig extends ResourceConfig {

    private static final Logger log = LoggerFactory.getLogger(JerseyConfig.class);

    @Autowired
    ApplicationContext appCtx;

    @PostConstruct
    public void setup() {
        log.info("Rest classes found:");
        Map<String,Object> beans = appCtx.getBeansWithAnnotation(Path.class);
        for (Object o : beans.values()) {
            log.info(" -> " + o.getClass().getName());
            register(o);
        }
    }

}

With Spring Boot (+ Jersey 2) it can look like separate config class:

@Configuration
public class RestBeansConfiguration {
    private static final Logger LOG = LoggerFactory.getLogger(RestBeansConfiguration.class);

    @Inject
    private ApplicationContext appCtx;

    @Bean
    public ResourceConfigCustomizer jersey() {
        return config -> {
            LOG.info("Jersey resource classes found:");
            appCtx.getBeansWithAnnotation(Path.class).forEach((name, resource) -> {
                LOG.info(" -> {}", resource.getClass().getName());
                config.register(resource);
            });
        };
    }
}

thx @matejsp for hint!

Are there any work around?
I run into the same issue

org.springframework.boot:spring-boot-starter-jersey:jar:1.4.0.RELEASE:compile
+- org.glassfish.jersey.core:jersey-server:jar:2.23.1:compile
| +- org.glassfish.jersey.core:jersey-client:jar:2.23.1:compile
| +- org.glassfish.jersey.media:jersey-media-jaxb:jar:2.23.1:compile
+- org.glassfish.jersey.containers:jersey-container-servlet:jar:2.23.1:compile
+- org.glassfish.jersey.ext:jersey-bean-validation:jar:2.23.1:compile
+- org.glassfish.jersey.ext:jersey-spring3:jar:2.23.1:compile
- org.glassfish.jersey.media:jersey-media-json-jackson:jar:2.23.1:compile
+- org.glassfish.jersey.ext:jersey-entity-filtering:jar:2.23.1:compile
Caused by: org.glassfish.jersey.server.internal.scanning.ResourceFinderException: java.io.FileNotFoundException: C:\sandbox\playground\JKD\hjd.jar!\BOOT-INF\classes (The system cannot find the path specified)
        at org.glassfish.jersey.server.internal.scanning.JarZipSchemeResourceFinderFactory.create(JarZipSchemeResourceFinderFactory.java:89)
        at org.glassfish.jersey.server.internal.scanning.JarZipSchemeResourceFinderFactory.create(JarZipSchemeResourceFinderFactory.java:65)
        at org.glassfish.jersey.server.internal.scanning.PackageNamesScanner.addResourceFinder(PackageNamesScanner.java:282)
        at org.glassfish.jersey.server.internal.scanning.PackageNamesScanner.init(PackageNamesScanner.java:198)
        at org.glassfish.jersey.server.internal.scanning.PackageNamesScanner.<init>(PackageNamesScanner.java:154)
        at org.glassfish.jersey.server.internal.scanning.PackageNamesScanner.<init>(PackageNamesScanner.java:110)
        at org.glassfish.jersey.server.ResourceConfig.packages(ResourceConfig.java:680)
        at org.glassfish.jersey.server.ResourceConfig.packages(ResourceConfig.java:660)

Are there any work around?

Yes, please see the release notes.

Checked, it does not work. In my case it no need to scan a third party jar. it's the most simple case.

@kcheng-mvp "it does not work" is not very helpful and this issue is closed. If you believe you've found a bug in Spring Boot, please create a separate issue with a sample that demonstrates the issue. Thanks!

Instead of using the packages("com.example") from Jersey, I use my own method (no spring needed):

public void scan(String... packages) {
    for (String pack : packages) {
        Reflections reflections = new Reflections(pack);
        reflections.getTypesAnnotatedWith(Provider.class)
                .parallelStream()
                .forEach((clazz) -> {
                    logger.debug("New provider registered: " + clazz.getName());
                    register(clazz);
                });
        reflections.getTypesAnnotatedWith(Path.class)
                .parallelStream()
                .forEach((clazz) -> {
                    logger.debug("New resource registered: " + clazz.getName());
                    register(clazz);
                });
    }
}

please refer to spring doc.

the Maven Plugin you would add the following configuration:

 <build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <requiresUnpack>
                    <dependency>
                        <groupId>org.jruby</groupId>
                        <artifactId>jruby-complete</artifactId>
                    </dependency>
                </requiresUnpack>
            </configuration>
        </plugin>
    </plugins>
</build>

Approach with Reflections scanning proposed by @brice-ruppen is a way faster on SB 2.0.6 than appCtx.getBeansWithAnnotation(Path.class):

    protected void scanPackages(final Class<? extends Annotation> annotation, String... packages) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        int[] counter = {0};
        for (String pack : packages) {
            Reflections reflections = new Reflections(pack);
            reflections.getTypesAnnotatedWith(annotation)
                .forEach((clazz) -> {
                    counter[0]++;
                    log.debug("New resource registered: " + clazz.getName());
                    register(clazz);
                });
        }
        stopWatch.stop();
        log.info("Registered {} @{} resources  in {} sec: packages = {}",
            counter[0], annotation.getSimpleName(), stopWatch.getTotalTimeSeconds(), Arrays.toString(packages));
    }

Used as:

    @Bean
    @Primary
    public ResourceConfig jerseyConfig() {
        return new JerseyResourceConfig() {
            {
                scanPackages(Path.class, "com.conserviscorp", "com.hiqo");
            }
        };
    }

The only change we had to do was to avoid parallelStream since it caused NPEs on different resources:

Caused by: java.lang.NullPointerException: null
    at org.glassfish.jersey.model.internal.CommonConfig.processFeatureRegistration(CommonConfig.java:500)
    at org.glassfish.jersey.model.internal.CommonConfig.register(CommonConfig.java:409)
    at org.glassfish.jersey.server.ResourceConfig.register(ResourceConfig.java:422)
    at 

Calling appCtx.getBeansWithAnnotation(Path.class) prior to ApplicationContext initialized through a ResourceConfigCustomizer increased startup time by 100 seconds in a relatively big project with 3k+ spring beans. Although it would be nice to reuse already initialized beans as singletons in jersey resource config.

getBeansWithAnnotation
@matejsp
Would it matter if you are using JAXB instead of POJO Support? Can this be omitted? getFeatures().put("com.sun.jersey.api.json.POJOMappingFeature", true);

Was this page helpful?
0 / 5 - 0 ratings