2

I've just been following this tutorial to create a REST API using Jersey on Jetty and I like the result. Everything works fine. But if I run the Gradle shadowJar task to generate a fat jar file this works fine and the file also runs but ends with an error message when a request is made:

$ java -jar build/libs/sample-all.jar 
[main] INFO org.eclipse.jetty.util.log - Logging initialized @161ms to org.eclipse.jetty.util.log.Slf4jLog
[main] INFO org.eclipse.jetty.server.Server - jetty-9.4.z-SNAPSHOT
[main] INFO org.eclipse.jetty.server.handler.ContextHandler - Started o.e.j.s.ServletContextHandler@5824a83d{/,null,AVAILABLE}
[main] INFO org.eclipse.jetty.server.AbstractConnector - Started ServerConnector@2ddc9a9f{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
[main] INFO org.eclipse.jetty.server.Server - Started @1547ms
Dez 12, 2018 10:05:07 PM org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
SCHWERWIEGEND: MessageBodyWriter not found for media type=application/json, type=class com.dovydasvenckus.jersey.greeting.Greeting, genericType=class com.dovydasvenckus.jersey.greeting.Greeting.

So in the compiled JAR it seems to me, that some e.g. lib is missing. I looked around in the web and everybody suggests to add org.glassfish.jersey.media:jersey-media-json-jackson:2.27 (or some variations) to make it work. But this does not work for me. My build.gradle looks like:

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'

sourceCompatibility = 1.8
mainClassName = 'com.dovydasvenckus.jersey.JerseyApplication'

ext {
    slf4jVersion = '1.7.25'
    jettyVersion = '9.4.6.v20170531'
    jerseyVersion = '2.27'
}

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.1'
    }
}

repositories {
    jcenter()
}

dependencies {
    compile "org.slf4j:slf4j-api:${slf4jVersion}"
    compile "org.slf4j:slf4j-simple:${slf4jVersion}"

    compile "org.eclipse.jetty:jetty-server:${jettyVersion}"
    compile "org.eclipse.jetty:jetty-servlet:${jettyVersion}"

    compile "org.glassfish.jersey.core:jersey-server:${jerseyVersion}"
    compile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jerseyVersion}"
    compile "org.glassfish.jersey.containers:jersey-container-jetty-http:${jerseyVersion}"
    compile "org.glassfish.jersey.media:jersey-media-json-jackson:${jerseyVersion}"
    compile "org.glassfish.jersey.inject:jersey-hk2:${jerseyVersion}"

    compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.27'

    compile "org.glassfish.jersey.media:jersey-media-moxy:2.27"

    compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.27'

}

jar { manifest { attributes 'Main-Class': "${mainClassName}" } }

Any ideas to make it work?

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
mythbu
  • 786
  • 1
  • 7
  • 20
  • Are you building an uber jar? If so, then you need to register the JacksonFeature with your application. With Maven, we would [solve the cause of this problem with the maven-shade-plugin](https://stackoverflow.com/a/37737650/2587435). Im not sure what the equivalent would be with Gradle. I know there is something out there, just not sure what it is. – Paul Samsotha Dec 12 '18 at 21:25
  • If [this](https://github.com/johnrengelman/shadow/issues/105) is the plugin you're using, try to follow the instructions given in the issue. Mainly the `mergeServiceFiles()` seems like what you need. From the name of it, it sound like the same transformer in the maven-shade-plugin. – Paul Samsotha Dec 12 '18 at 21:38
  • @PaulSamsotha: Yes, I'm building an uber jar (or trying). I'm using this plugin: com.github.johnrengelman.shadow which is pretty standard. – mythbu Dec 12 '18 at 21:53
  • Use the [`mergeServiceFiles()`](https://imperceptiblethoughts.com/shadow/configuration/merging/#merging-service-descriptor-files). This is the equivalent of the service file transformer of the Maven plugin I mentioned in my linked post. You can read my answer to see why it is needed. – Paul Samsotha Dec 12 '18 at 22:29

1 Answers1

3

As mentioned in my answer in MessageBodyProviderNotFoundException while running jar from command line, there is a "services file", namely org.glassfish.jersey.internal.spi.AutoDiscoverable, which is included in many jars. The purpose of this file is to allow Jersey (and other third party) jars to provide some auto-registration for features included in those jars. This includes registration of the JacksonFeature, which registers the JSON providers that handle (de)serialization.

The problem with creating fat (uber) jars is that there can only be one of those files (you can't have more than one file of the same name). So with all the jars included in the fat jar, only one service file will be included.

With Maven, we would use the maven-shade-plugin, which has transformers that allow for transforming parts of the build. The shade plugin has a ServicesResourceTransformer which takes care of concatenating the contents of the service files into one service file. The plugin you are using for Gradle, Shadow, has the same facility. You configure it by calling mergeServiceFiles() in the configuration. I don't really work with Gradle, but also stated in this issue, the recommended configuration for handling both the service files transformation and the main class manifest transformation is the following

shadowJar {
  mergeServiceFiles()
  manifest {
    attributes 'Main-Class': 'com.my.Application'
  }
}

So I am assuming, with the above configuration, you can also remove

jar { manifest { attributes 'Main-Class': "${mainClassName}" } }

as the Shadow plugin will take care of building the manifest. Again, I don't really use Gradle, so this is just an assumption; but it sounds right.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • This does the trick. Thx. P.S.: For everybody else looking for this: make sure to clean the cache of builded files. Otherwise it might not work correctly. – mythbu Dec 13 '18 at 05:43