60

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.

skaffman
  • 398,947
  • 96
  • 818
  • 769
Ralph
  • 31,584
  • 38
  • 145
  • 282

4 Answers4

107

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; 
    } 

}
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • 1
    I actually figured out how to do the translation using @XmlJavaTypeAdapter. With this, I have been able to adapter several different Java classes that required special treatment in my code. Thanks. – Ralph Jul 06 '10 at 11:01
  • 6
    You need to be very careful with SimpleDateformat as it is not threadsafe! I'm not sure if JAXB creates a new object everytime for the adapter. – Adam Gent Mar 02 '11 at 04:16
  • For more information see: http://bdoughan.blogspot.com/2011/01/jaxb-and-datetime-properties.html – bdoughan Mar 19 '11 at 01:36
  • @Gepsens - If you're interested in learning more about `XmlAdapter` I have quite a few examples on my blog: http://blog.bdoughan.com/search/label/XmlAdapter – bdoughan Oct 17 '11 at 01:04
  • Oh thanks, I ended up using your code and adapted it for the Time (sql package) class. Your article is very nice, I usually tweak the jaxb code to my needs when which I know is bad practice compared to using bindings. – Gepsens Oct 24 '11 at 14:34
  • 1
    "yyyy-MM-dd'T'HH:mm:ss" is the magic sauce to get an iOS NSDate to upload properly to a RESTEasy jax-rs generated java.util.Date without any futzing around. – user798719 Mar 28 '13 at 21:27
  • Put the XMLJavaTypeAdapter on the getter if using JPA annotations in the same class: http://www.objectpartners.com/2010/01/25/using-jpa-and-jaxb-annotations-in-the-same-object/ – Jason Sep 19 '13 at 21:53
  • I suggest setting the timezone on the SimpleDateFormat to avoid varying results depending on the current locale. – Emmanuel Bourg Mar 05 '14 at 14:26
  • Do you know why the `T` separator is mandatory in JAXB? [It seems to be optional (by "mutual agreement") in the ISO 8601 standard](http://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations) – Lukas Eder Feb 22 '15 at 17:50
11

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.

skaffman
  • 398,947
  • 96
  • 818
  • 769
  • 2
    JAXB can certainly marshal java.util.Date, only the format is "yyyy-MM-ddTHH:mm:ss" matching xsd:dateTime. The EclipseLink JAXB implementation (MOXy) can also handle the java.sql.Date/Time/Timestamp types. – bdoughan Jul 08 '10 at 13:27
3

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>
Andrea Luciano
  • 461
  • 3
  • 5
3

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);
    }

}
dermoritz
  • 12,519
  • 25
  • 97
  • 185
  • I got the following error: org.xml.sax.SAXParseException; lineNumber: 0; columnNumber: 0; cvc-datatype-valid.1.2.1: '05/10/2017 00:00:00' is not a valid value for 'dateTime' – Aguid Oct 10 '17 at 10:01
  • your string '05/10/2017 00:00:00' has not the format expected: "yyyy-MM-dd'T'HH:mm:ss" - you should either change input or format – dermoritz Oct 10 '17 at 10:56
  • INFRAXML 2.2: javax.xml.bind.MarshalException - with linked exception: [org.xml.sax.SAXParseException; lineNumber: 0; columnNumber: 0; cvc-datatype-valid.1.2.1: '2017-10-10T14:59:34' is not a valid value for 'date'.] – Aguid Oct 10 '17 at 13:00