Pisząc REST API z wykorzystaniem JAX-RS nie da się obejść bez serializacji i deserializacji danych z żądania i dla odpowiedzi. W większości wypadków wystarczająca jest domyślna implementacja, która nie wymaga żadnej dodatkowej konfiguracji. Schody pojawiają się wtedy, gdy chcemy jakiś obiekt traktować w specyficzny sposób, szczególnie w przypadku daty, która może być podawana w różnych formatach. Na szczęście z pomocą przychodzi standard JAXB, który m.in. dzięki adapterom, pozwala na precyzyjne określenie sposobu traktowania danych przychodzących i wychodzących.

Przyjmijmy, że mamy prosty serwis, który pobiera dane osoby (deserializując dane) i zwraca je (serializując ponownie):

@POST
@Path(value = "echo")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public PersonDto echo(PersonDto person) {
	return person;
}

gdzie typ PersonDto ma jedno z pól zawierające datę:

import java.util.Date;

public class PersonDto {

	private String firstName;

	private String lastName;

	private Date birthday;

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public Date getBirthday() {
		return birthday;
	}

	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}

}

Przy domyślnej konfiguracji, data powinna być przekazana w formacie akceptowanym przez konstruktor public Date(String s) (swoją drogą oznaczony jako @Deprecated):

{
	"firstName": "Jan",
	"lastName": "Kowalski",
	"birthday": "Sat, 12 Aug 1995 13:30:00 GMT"
}

Ponadto w odpowiedzi dostaniemy czas epoch:

{
	"firstName": "Jan",
	"lastName": "Kowalski",
	"birthday": 808234200000
}

Oczywiście nic nie stoi na przeszkodzie, że przekazywać na wejściu czas również w formacie epoch, co uchroni nas przed niespójnością. Może się jednak zdarzyć sytuacja, kiedy to nas nie satysfakcjonuje, bo chcemy podawać i otrzymywać czas w specyficznym formacie.

Dla własnego adaptera dla typu wystarczy, że będzie rozszerzał klasę abstrakcyjną javax.xml.bind.annotation.adapters.XmlAdapter:

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

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

public class DateAdapter extends XmlAdapter<String, Date> {

	private static final String DATE_FORMAT = "dd.MM.yyyy";

	@Override
	public Date unmarshal(String value) throws Exception {
		DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
		return dateFormat.parse(value);
	}

	@Override
	public String marshal(Date value) throws Exception {
		DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
		return dateFormat.format(value);
	}

}

Aby wskazać, że pole w klasie powinno korzystać z danej serializacji, wystarczy użyć adnotacji javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter:

@XmlJavaTypeAdapter(value = DateAdapter.class)
private Date birthday;

Teraz nie pozostaje nic innego jak tylko rozpocząć korzystanie z nowego formatu daty:

{
	"firstName": "Jan",
	"lastName": "Kowalski",
	"birthday": "12.08.1995"
}

Warto zwrócić uwagę, że nic nie stoi na przeszkodzie, żeby zwracać dane w innym formacie niż pobrane. Tutaj jedyne co może być przeciwskazaniem to zdrowy rozsądek. Jeśli nie robimy konwertera dat pomiędzy różnymi formatami to po co sobie utrudniać życie…​