Search code examples
javajacksonjax-rs

Jackson 2.16.1 strange behavior


I have migrated an application from Java 8 and Jackson 2.9 to Java 11 (Jakarta 10.0) and Jackson 2.16. The application is running on Open Liberty. I have found a strange behavior which I do not know how to solve. I have a piece of code which retrieves information from a database. This piece of code is used in two different REST calls which return either one element or the complete list of elements.

    public List<Assessment> getAssessments() throws Exception {
        StringBuffer sb = getAssessmentsQuery();
        return doAssessments(sb.toString(), new Object[] {});
    }

    public Assessment getAssessment(String id) throws Exception {

        Assessment a = null;

        StringBuffer sb = getAssessmentsQuery();
        sb.append("AND F.ID=?");
        List<Assessment> aa = doAssessments(sb.toString(),new String[] {id});
        
        if (aa!=null && aa.size()>0) {
            a = aa.get(0);
        }
        return a;
    }

So the code retrieving the information is exactly the same. The REST calls are implemented as:

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getAssessments() {
        ResponseBuilder rb = Response.serverError();

        try {
            DB2Assessments dba = new DB2Assessments();
            List<Assessment> aa = dba.getAssessments();
            GZIPStreamingOutput gzso = new GZIPStreamingOutput(aa);
            rb = Response.ok().entity(gzso).
                variant(new Variant(MediaType.APPLICATION_JSON_TYPE,
                Locale.getDefault(), "gzip"));
        } catch (Exception e) {
            rb.entity(e.getMessage());
        }
        return rb.build();
    }

    @GET
    @Path ("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getAssessment(@PathParam("id") String id) {
        ResponseBuilder rb = Response.serverError();

        try {
            DB2Assessments dba = new DB2Assessments();
            Assessment a = dba.getAssessment(id);
            if (a==null) {
                rb = Response.status(404);
            } else {
                rb = Response.ok().entity(a);
            }
        } catch (Exception e) {
            rb.entity(e.getMessage());
        }
        return rb.build();
    }

The results in the response are different for each of the calls. Obviously one is an array and the other is a single object. But the names of the fields and the formats are different.

Single object:

enter image description here

Array of objects:

enter image description here

As you can see, the completed field for the single object (which is a Date) has been formatted as a yyyy-mm-dd string with an ending Z. But in the object array the data is included in nanoseconds. Additionally, the uname field has different casing.

The POJO class implementing the Assessment object is the same. I will include it here for completeness.

public class Assessment extends Base {

    private Hashtable<String, Object> properties;
    private String uName;
    private String quarter;
    private int year;
    private Date completed;
    private String status;
    
    public Document() {
        properties = new Hashtable<String, Object>();
    }

    public String getProperty(String key) {
        return (String)properties.getOrDefault(key,"");
    }

    public Integer getNumProperty(String key) {
        return (Integer)properties.getOrDefault(key,0);
    }

    public void setProperty(String key, Object value) {
        if (value!=null) {
            properties.put(key, value);
        }
    }

    public Hashtable<String, Object> getProperties() {
        return properties;
    }

    public void setProperties(Hashtable<String, Object> properties) {
        this.properties = properties;
    }

    public String getUName() {
        return uName;
    }

    public void setUName(String uName) {
        this.uName = uName;
    }

    public String getQuarter() {
        return quarter;
    }

    public void setQuarter(String quarter) {
        this.quarter = quarter;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public Date getCompleted() {
        return completed;
    }

    public void setCompleted(Date completed) {
        this.completed = completed;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getPeriod() {
        return year+"/"+quarter;
    }

Not only the code is the same. This application has been working for a few years in the old platform.

Yes, I know Jackson has converters and formatters and ... But why is the behavior not consistent?

Any ideas/comments/suggestions?

Thanks in advance!

UPDATE 2024-02-13
I have found that Jakarta is not using Jackson. In fact, JavaEE7 was not using Jackson either. JavaEE7 was using Apache CXF for serializing and Jakarta is using RESTEasy. The required annotations are now those provided by Jsonb (@JsonbProperty instead of the @JsonProperty/@JsonGetter/@JsonSetter from Jackson).

So my POJO is now:

    @JsonbProperty("uname")
    private String uName;
    @JsonbDateFormat("yyyy-MM-dd") 
    private Date completed;

    @JsonbProperty("uname")
    public String getUName() {
        return uName;
    }

    public void setUName(String uName) {
        this.uName = uName;
    }

    @JsonbDateFormat("yyyy-MM-dd") 
    public Date getCompleted() {
        return completed;
    }

    public void setCompleted(Date completed) {
        this.completed = completed;
    }

That solves the problem with a single object. But when returning an array, the annotation is ignored and I still get my completed date in nanoseconds.

enter image description here

Any comments or suggestions are welcome.


Solution

  • After lots of testing I found the problem.

    In the beginning, I was not properly tagging the POJO object. Once the notations where the right ones but not working either I tried with a custom serializer.

    Single objects were fine but no luck with lists or arrays.

    A few minutes ago I found the problem. I was using a helper to compress the output. So, even though I was returning a JSON object, it was not being processed by JSON-B.

    Lesson learned: compression is performed before formatting has been applied.

    GZIPStreamingOutput gzso = new GZIPStreamingOutput(aa);
    rb = Response.ok().entity(gzso).
        variant(new Variant(MediaType.APPLICATION_JSON_TYPE,
        Locale.getDefault(), "gzip"));
    

    I have not had this problem in the past because formatting was not required.