First of all, JAXB is a Java standard for binding XML schemas to Java classes. It allows you to convert Java objects to XML documents, and vice versa, based on JAXB annotations on the corresponding Java classes. JAXB doesn't cover JSON but there are libraries that allow you to convert Java objects to JSON (and vice versa) based on the very same JAXB annotations that are used for defining XML bindings. One such library is Jersey's JSON library (jersey-json) which internally uses the Jackson library.
As you'll see in the following, JAXB can also be used together with immutable domain or resource models based on Scala case classes. There's no need to pollute them with getters and setters or Java collections from the
java.utilpackage. Necessary conversions from Scala collections or other type constructors (such as
Option, for example) to Java types supported by JAXB can be defined externally to the annotated model (and reused). At the end of this blog post, I'll also show some examples how to develop JAXB-based XML and JSON APIs with the Play Framework.
ModelIn the following, I'll use a model that consists of the single
case class Person(fullname: String, username: Option[String], age: Int). To define
Personas root element in the XML schema, the following JAXB annotations should be added.
Persona root element in the XML schema and
@XmlAccessorType(XmlAccessType.FIELD)instructs JAXB to access fields directly instead of using getters and setters. But before we can use the
Personclass with JAXB a few additional things need to be done.
- A no-arg constructor or a static no-arg factory method must be provided, otherwise, JAXB doesn't know how to create
Personinstances. In our example we'll use a no-arg constructor.
- A person's
fullnameshould be mandatory in the corresponding XML schema. This can be achieved by placing an
@XmlElement(required=true)annotation on the field corresponding to the
- A person's
usernameshould be an optional
Stringin the corresponding XML schema i.e. the
usernameelement of the complex
Persontype should have an XML attribute
minOccurs="0". Furthermore, it should be avoided that
scala.Optionappears as complex type in the XML schema. This can be achieved by providing a type adapter from
Stringvia the JAXB
We can implement the above requirements by defining and annotating the
Personclass as follows:
Let's dissect the above code a bit:
- The no-arg constructor on the
Personclass is only needed by JAXB and should therefore be declared private so that it cannot be accessed elsewhere in the application code (unless you're using reflection like JAXB does).
- Placing JAXB annotations on fields of a case class is a bit tricky. When writing a case class, usually only case class parameters are defined but not fields directly. The Scala compiler then generates the corresponding fields in the resulting .class file. Annotations that are placed on case class parameters are not copied to their corresponding fields, by default. To instruct the Scala compiler to copy these annotations, the Scala
@fieldannotation must be used in addition. This is done in the custom annotation type definitions
xmlTypeAdapter. They can be used in the same way as their dependent annotation types
XmlJavaTypeAdapter, respectively. Placing the custom
@xmlElementannotation on the
fullnameparameter will cause the Scala compiler to copy the dependent
@XmlElementannotation (a JAXB annotation) to the generated
fullnamefield where it can be finally processed by JAXB.
- To convert between
Option[String](on Scala side) and
String(used by JAXB on XML schema side) we implement a JAXB type adapter (interface
XmlAdapter). The above example defines a generic
OptionAdapter(that can also be reused elsewhere) and a concrete
StringOptionAdapterused for the optional
usernameparameter. Please note that annotating the
@xmlTypeAdapter(classOf[OptionAdapter[String]])is not sufficient because JAXB will not be able to infer
Stringas the target type (JAXB uses reflection) and will use
Objectinstead (resulting in an XML
anyTypein the corresponding XML schema). Type adapters can also be used to convert between Scala and Java collection types. Since JAXB can only handle Java collection types you'll need to use type adapters should you want to use Scala collection types in your case classes (and you really should). You can find an example here.
We're now ready to use the
Personclass to generate an XML schema and to convert
Personobjects to and from XML or JSON. Please note that the following code examples require JAXB version 2.2.4u2 or higher, otherwise the
OptionAdapterwon't work properly. The reason is JAXB issue 415. Either use JDK 7u4 or higher which already includes this version or install the required JAXB version manually. The following will write an XML schema, generated from the
Personclass, to stdout:
The result is:
Personobject to XML can be done with
Unmarshalling creates a
Personobject from XML.
We have implemented
StringOptionAdapterin a way that an empty
personXml1would also yield
Noneon Scala side. Creating JSON from
Personobjects can be done with the
JSONJAXBContextfrom Jersey's JSON library.
which prints the following to stdout:
Unmarshalling can be done with the
JSONConfigurationobject provides a number of configuration options that determine how JSON is rendered and parsed. Refer to the official documentation for details.
Play and JAXBThis section shows some examples how to develop JAXB-based XML and JSON APIs with the Play Framework 2.0. It is based on JAXB-specific body parsers and type class instances defined in trait
JaxbSupportwhich is part of the event-sourcing example application (Play-specific work is currently done on the play branch). You can reuse this trait in other applications as is, there are no dependencies to the rest of the project (update: except to
SysError). To enable JAXB-based XML and JSON processing for a Play web application, add
JaxbSupportto a controller object as follows:
JSONJAXBContextmust be in scope for both XML and JSON processing. For XML processing alone, it is sufficient to have an implicit JAXBContext.
XML and JSON Parsing
JaxbSupportprovides Play-specific body parsers that convert XML or JSON request body to instances of JAXB-annotated classes. The following action uses a JAXB body parser that expect an XML body and tries to convert it to a
Personinstance (using a JAXB unmarshaller).
If the unmarshalling fails or the request
Content-Typeis other than
400status code (bad request) is returned. Converting a JSON body to a
Personinstance can be done with the
If the body parser should be chosen at runtime depending on the
Content-Typeheader, use the dynamic
jaxb.parsebody parser. The following action is able to process both XML and JSON bodies and convert them to a
JaxbSupportalso implements the following related body parsers
XML and JSON RenderingFor rendering XML and JSON, JaxbSupport provides the wrapper classes
Jaxb. The following action renders an XML response from a
Personobject (using a JAXB marshaller):
renders a JSON response from a
Personobject. If you want to do content negotiation based on the
Acceptrequest header, use the
Jaxb requires an implicit
requestin context for obtaining the
Acceptrequest header. If the
AcceptMIME type is
text/xmlan XML representation is returned, if it is
application/jsona JSON representation is returned. Further
JaxbSupportapplication examples can be found here.