Hot questions for Using GlassFish in jackson

Question:

I'm using Jersey and Jackson to create a simple JSON API.

Some of the objects being serialized have custom enum fields. By default, those enums are converted to a string based on the enum vale -- I would like the enums to have slightly more complex serializations.

I'm using Jackson annotations within the enum, but the endpoint seems to be ignoring them. I've been spinning my wheels trying to figure out where the issue is, and now I turn to you.

Enum Code

package org.example.code;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonFormat(shape= JsonFormat.Shape.OBJECT)
public enum ExampleEnum {
    YES (1, "Yes indeed"),
    NO (2, "No way buddy")

    private final Integer code;
    private final String description;

    ExampleEnum(final Integer code, final String description) {
        this.code = code;
        this.description = description;
    }

    @JsonProperty("code")
    public Integer getCode() {
        return code;
    }
    @JsonProperty("description")
    public String getDescription() {
        return description;
    }
}

API Code

package org.example.webservice.impl;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.example.code.ExampleEnum;


@Path("/example")
public class ExampleService {   
    @GET
    @Path("/test")
    @Produces({MediaType.APPLICATION_JSON})
    public ExampleEnum getExampleEnum() {
        return ExampleEnum.YES;
    }
}

When I call the endpoint example/test the output is YES What I want is the output to be something along the lines of { code: 1, description: "Yes indeed" }

Configuration files are below...

pom.xml

<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>org.example</groupId>
    <artifactId>api</artifactId>
    <version>0.0.1</version>
    <packaging>war</packaging>

    <name>example API</name>
    <url>http://example.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>javax.ws.rs</groupId>
            <artifactId>javax.ws.rs-api</artifactId>
            <version>2.0.1</version>
        </dependency>

        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.2</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.6.0</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet-core</artifactId>
            <version>2.18</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <url>http://localhost:8080/manager/text</url>
                    <server>TomcatServer</server>
                    <path>/example</path>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <display-name></display-name> 
  <servlet>
    <display-name>Example Servlet</display-name>
    <servlet-name>Example Servlet</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>org.example.webservice.impl</param-value>
    </init-param>
    <init-param>
        <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>jersey.config.server.provider.classnames</param-name>
      <param-value>org.glassfish.jersey.filter.LoggingFilter;org.glassfish.jersey.media.multipart.MultiPartFeature</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Example Servlet</servlet-name>
    <url-pattern>/services/*</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

Answer:

So a Few things:

  • com.sun.jersey.api.json.POJOMappingFeature is for Jersey 1.x, so you can get rid of that.

  • MOXy is the default provider in Glassfish, so if you want to use Jackson, then you need to disable MOXy. You can do so by adding this <init-param>

    <init-param>
        <param-name>jersey.config.server.disableMoxyJson</param-name>
        <param-value>true</param-value>
    </init-param>
    

    If you are using a ResourceConfig for your configuration, you can use the property

    public class JerseyConfig extends ResourceConfig {
        public JerseyConfig() {
            property(ServerProperties.MOXY_JSON_FEATURE_DISABLE, true);
        }
    }
    

    The constant value is actually jersey.config.server.disableMoxyJson.

  • Then add the Jackson provider

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>${jersey2.version}</version>
        <scope>provided</scope>
    </dependency>
    

    The Jersey version should match whatever version you are using for your project. One very important thing to note though is that if you are using Glassfish, you SHOULD put all your Jersey dependencies in a <scope>provided</scope> because the server already has the jars in its library. You do not want your project dependencies to conflict with the jars that are already in the server.

  • Next you need to register the Jackson provider with your app. If you are using web.xml you can add the JacksonFeature to the list on the ...classnames property

    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>
            org.glassfish.jersey.filter.LoggingFilter,
            org.glassfish.jersey.media.multipart.MultiPartFeature,
            org.glassfish.jersey.jackson.JacksonFeature
        </param-value>
    </init-param>
    

    If you are using a ResourceConfig for your configuration, then you can just register the JacksonFeature

    public class JerseyConfig extends ResourceConfig {
        public JerseyConfig() {
            register(JacksonFeature.class);
        }
    }
    

Another thing that is unrelated to this issue that I might point out, is that Glassfish 4 already has a Jersey implementation, which is a very old 2.x version, maybe 2.0. When you add runtime Jersey dependencies, they might conflict. I would do one of two things, either put all the Jersey dependencies in a provided <scope> or if you have requirements for later version functionality, you may want to look into updating the Jersey version in Glassfish. Have a look at Updating Jersey 2 in GlassFish 4

Question:

While I restarting the Glassfish 3.0 server from NetBeans 8.0 after adding a RESTful web service, I got this exception and couldn't request the web service!!

Glassfish 3.0: java.lang.ClassNotFoundException: org.codehaus.jackson.jaxrs.JacksonJsonProvider

I checked the Maven Pom.xml for jersey dependency and the tag is already exist!

 <dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-json-provider</artifactId>
    <version>2.4.0</version>
</dependency>

Answer:

I don't know your exact setup but the problem is that you are providing Jersey/Jackson 2.x libs but you are obviously using Jersey/Jackson 1.x.

Note that Glassfish 3.x comes with Jersey/Jackson 1.x by default (Glassfish 4.x comes with Jersey/Jackson 2.x by default).

The error message shows it can't find class org.codehaus.jackson.jaxrs.JacksonJsonProvider.

The org.codehaus classes are indicating Jersey/Jackson 1.x. In Jersey/Jackson 2.x the classes are in the package com.fasterxml (like in the dependency you are providing).

Try the following:

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-jaxrs</artifactId>
    <version>1.9.13</version>
</dependency>

and remove the other dependency.

Question:

My Java-backend running on a newly setup Debian 9 machine does not work properly anymore. Some calls are working, others fail with this error stack:

org.glassfish.jersey.server.ContainerException: java.lang.NoSuchFieldError: ACCEPT_CASE_INSENSITIVE_PROPERTIES at org.glassfish.jersey.servlet.internal.ResponseWriter.rethrow(ResponseWriter.java:278) at org.glassfish.jersey.servlet.internal.ResponseWriter.failure(ResponseWriter.java:260) at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:509) at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:334) at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271) at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267) at org.glassfish.jersey.internal.Errors.process(Errors.java:315) at org.glassfish.jersey.internal.Errors.process(Errors.java:297) at org.glassfish.jersey.internal.Errors.process(Errors.java:267) at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317) at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305) at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154) at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473) at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427) at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388) at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341) at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228) at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:686) at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:501) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137) at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:557) at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231) at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1086) at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:428) at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193) at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1020) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135) at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116) at org.eclipse.jetty.server.handler.RequestLogHandler.handle(RequestLogHandler.java:68) at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116) at org.eclipse.jetty.server.Server.handle(Server.java:370) at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:489) at org.eclipse.jetty.server.AbstractHttpConnection.content(AbstractHttpConnection.java:960) at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.content(AbstractHttpConnection.java:1021) at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:865) at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:240) at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82) at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:668) at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:52) at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608) at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543) at java.lang.Thread.run(Thread.java:748)

It does not give me a single hint where in my code this is happening, on other maschines, with the same import this is working.

I cannot find any difference in the used libraries compared to the server where the project is working fine. Also the same java version is used on the server and on my machine.

Anyone with any ideas? Thanks!


Answer:

It's most likely a classpath issue, where Glassfish somehow picks up an older version of Jackson. The ACCEPT_CASE_INSENSITIVE_PROPERTIES field was introduced in Jackson 2.5, so look for earlier versions in the classpath.

Try logging the Jackson version by finding a class that implements the Versioned interface (e.g. ObjectMapper). That should tell you whether the classpath theory holds up.

Question:

I got the following error message every time I try to call my REST service

[2016-09-01T16:27:37.782+0200] [Payara 4.1] [SEVERE] []   [org.glassfish.jersey.message.internal.WriterInterceptorExecutor] [tid: _ThreadID=28 _ThreadName=http-listener-1(3)] [timeMillis: 1472740057782] [levelValue: 1000] [[MessageBodyWriter not found for media type=application/json, type=class xxx.JsonClass, genericType=class xxx.JsonClass.]]

Here's the REST service (stripped to the relevant part):

import javax.ejb.EJB;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;


@Path("/service")
public class Service {

  @GET
  @Path("/callme")
  @Produces(MediaType.APPLICATION_JSON)
  public JsonClass callme(//
      @QueryParam("test") final String test, //
       ....) {
    return new JsonClass();
  }
}

The JSON Class

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

public class JsonClass {

  private String test;

  public JsonClass(final String test....) {
   ...
  }

  @JsonProperty
  public String getTest() {
    return this.test;
  }
}

POM.xml (interesting parts)

<!-- DO NOT change the scope for jersey: https://java.net/jira/browse/JERSEY-1941 -->
<dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-json-jackson</artifactId>
  <version>2.8</version>
  <scope>provided</scope>
</dependency>

My setup is:

  • JDK8/JEE7 (build 1.8.0_51-b16)
  • Glassfish 4.1 Payara
  • Maven 3.2.5

This is what I tried so far:

  • MessageBodyWriter not found for media type=application/json -> Doesn't work because I run into a problem with Glassfish 4 and Weld (https://java.net/jira/browse/JERSEY-1941)
  • SEVERE: MessageBodyWriter not found for media type=application/json, type=class com.jersey.jaxb.Todo, genericType=class com.jersey.jaxb.Todo -> As seen in the POM.xml above it's already included and does not work
  • https://java.net/jira/browse/JERSEY-2715 -> Annotation @Produces is already there and doesn't help either
  • Obtaining "MessageBodyWriter not found for media type=application/json" trying to send JSON object through JAX-RS web service - GENSON with @XmlAttribute had not the desired effect
  • I tried to keep the JSON object as simple as possible to avoid problems with arrays and complex objects -> No change

I still think it's a dependency problem here. However I'm out of ideas what could be the problem.


Answer:

Unfortunately my last post was marked as duplicate although the problem and the solution was different. Therefore I'm posting a new question with the two solutions to hopefully help you avoid head banging at the table for several hours.

Preferred solution:

Apparently GF4 ships with MoxyJson which I didn't want to use. To integrate your own dependency - in my case Jackson - you need to disable the MoxyJson with the below code.

@ApplicationPath("/")
public class ApplicationConfig extends Application {

  /**
   * {@inheritDoc}
   */
  @Override
  public Map<String, Object> getProperties() {
    final Map<String, Object> properties = new HashMap<String, Object>();
    properties.put("jersey.config.server.disableMoxyJson", true);

    return properties;
  }
}

Then add your own dependencies, e.g. in my case only those two because the others are referenced by another lib I use.

<dependency>
  <groupId>com.fasterxml.jackson.jaxrs</groupId>
  <artifactId>jackson-jaxrs-json-provider</artifactId>
  <version>2.6.2</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-xml</artifactId>
  <version>2.6.2</version>
</dependency>

Finally I made the mistake of not setting a value to the @JsonProperty annotations which will cause a No MessageBodyWriter found exception. To avoid that use the following ony relevant getters of your class.

@JsonProperty("randomName")
public String getRandomName(){
...
}

Alternative:

Worse than above you'll need to disable MoxyJson, register each service individually, and fix a Bug when using ResourceConfig of GF.

@ApplicationPath("/")
public class ApplicationConfig extends ResourceConfig {

/**
* The default constructor.
*/
public ApplicationConfig() {

// Disable Moxy and use Jackson
this.property(ServerProperties.MOXY_JSON_FEATURE_DISABLE, true);

// Register own provider classes
this.register(Fully.Qualified.Path.To.Your.Service.class);

// Register Jackson provider
// Workaround for GF4.1 bug for details: https://java.net/jira/browse/GLASSFISH-21141
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JaxbAnnotationModule());
this.register(new JacksonJaxbJsonProvider(mapper, JacksonJaxbJsonProvider.DEFAULT_ANNOTATIONS));
}
}

You'll need an additional dependency for the ResourceConfig class.

 <dependency>
  <groupId>com.fasterxml.jackson.jaxrs</groupId>
  <artifactId>jackson-jaxrs-json-provider</artifactId>
  <version>2.6.2</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-xml</artifactId>
  <version>2.6.2</version>
</dependency>

<dependency>
  <groupId>org.glassfish.main.extras</groupId>
  <artifactId>glassfish-embedded-all</artifactId>
  <version>4.1.1</version>
  <scope>provided</scope>
</dependency>

Finally the same as above - be aware to use @JsonProperty with a set value.

Question:

Does Jackson modify ObjectMappers returned from a JAX-RS ContextResolver<ObjectMapper>?

i.e., if my ContextResolver<ObjectMapper> has a private ObjectMapper om field, should its getContext(Class) method return om.copy(), or just om?


Answer:

You should return just om. As far as I can see from the code, once the JAX-RS provider locates an ObjectMapper it deals with object reader and object writer instances which are fully thread safe.

StaxMan, please correct me if I'm wrong.