jaxb unmarshal timestamp

JavaDatetimeJaxb

Java Problem Overview


I cannot get JAXB to unmarshal a timestamp in a Resteasy JAX-RS server application.

My class looks like this:

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "foo")
public final class Foo {
    // Other fields omitted

    @XmlElement(name = "timestamp", required = true)
    protected Date timestamp;

    public Foo() {}

    public Date getTimestamp() {
        return timestamp;
    }

    public String getTimestampAsString() {
        return (timestamp != null) ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(timestamp) : null;
    }

    public void setTimestamp(final Date timestamp) {
        this.timestamp = timestamp;
    }

    public void setTimestamp(final String timestampAsString) {
        try {
            this.timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(timestampAsString);
        } catch (ParseException ex) {
            this.timestamp = null;
        }
    }
}

Any ideas?

Thanks.

Java Solutions


Solution 1 - Java

JAXB can handle the java.util.Date class. However it expects the format:

"yyyy-MM-dd'T'HH:mm:ss" instead of "yyyy-MM-dd HH:mm:ss"

If you want to use that date format I would suggest using an XmlAdapter, it would look something like the following:

import java.text.SimpleDateFormat;
import java.util.Date;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class DateAdapter extends XmlAdapter<String, Date> {

    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public String marshal(Date v) throws Exception {
        return dateFormat.format(v);
    }

    @Override
    public Date unmarshal(String v) throws Exception {
        return dateFormat.parse(v);
    }

}

You would then specify this adapter on your timestamp property:

import java.util.Date;

import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlAccessorType(XmlAccessType.NONE) 
@XmlRootElement(name = "foo") 
public final class Foo { 
    // Other fields omitted 

    @XmlElement(name = "timestamp", required = true) 
    @XmlJavaTypeAdapter(DateAdapter.class)
    protected Date timestamp; 

    public Foo() {} 

    public Date getTimestamp() { 
        return timestamp; 
    } 

    public void setTimestamp(final Date timestamp) { 
        this.timestamp = timestamp; 
    } 

}

Solution 2 - Java

JAXB cannot marshal Date objects directly, because they don't have enough information to be unambiguous. JAXB introduced the XmlGregorianCalendar class for this purpose, but it's very unpleasant to use directly.

I Suggest changing your timestamp field to be a XmlGregorianCalendar, and change your various methods to update this field while retaining the public interface you already have, where possible.

If you want to keep the Date field, then you'll need to implement your own XmlAdapter class to tell JAXB to how turn your Date to and from XML.

Solution 3 - Java

In order to get the XML marshaller to generate an xsd:date formatted as YYYY-MM-DD without defining an XmlAdapter I used this method to build an instance of javax.xml.datatype.XMLGregorianCalendar:

public XMLGregorianCalendar buildXmlDate(Date date) throws DatatypeConfigurationException {
	return date==null ? null : DatatypeFactory.newInstance().newXMLGregorianCalendar(new SimpleDateFormat("yyyy-MM-dd").format(date));
}

With the result I initialized the XMLGregorianCalendar field of the class generated by JAXB compiler (in Eclipse):

  Date now = new Date();
  ...
  report.setMYDATE(buildXmlDateTime(now));
  ...
  JAXBContext context = JAXBContext.newInstance(ReportType.class);
  Marshaller m = context.createMarshaller();
  m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
  m.marshal(new ObjectFactory().createREPORT(report), writer);

And obtained the tag formatted as expected:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<REPORT>
   ...
   <MY_DATE>2014-04-30</MY_DATE>
   ...
</REPORT>

Solution 4 - Java

Using this adapter should be thread safe:

public class DateXmlAdapter extends XmlAdapter<String, Date> {
    
    /**
     * Thread safe {@link DateFormat}.
     */
    private static final ThreadLocal<DateFormat> DATE_FORMAT_TL = new ThreadLocal<DateFormat>() {

        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        }

    };
    
    @Override
    public Date unmarshal(String v) throws Exception {
        return DATE_FORMAT_TL.get().parse(v);
    }

    @Override
    public String marshal(Date v) throws Exception {
        return DATE_FORMAT_TL.get().format(v);
    }

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRalphView Question on Stackoverflow
Solution 1 - JavabdoughanView Answer on Stackoverflow
Solution 2 - JavaskaffmanView Answer on Stackoverflow
Solution 3 - JavaAndrea LucianoView Answer on Stackoverflow
Solution 4 - JavadermoritzView Answer on Stackoverflow