Using JAXB in Java 11

Java Architecture for XML Binding (JAXB) is a software framework that allows Java developers to map Java classes to XML representations. JAXB provides two main features: the ability to marshal Java objects into XML and the inverse, i.e. to unmarshal XML back into Java objects. In other words, JAXB allows storing and retrieving data in memory in any XML format, without the need to implement a specific set of XML loading and saving routines for the program's class structure.

JAXB 1.0 was developed under the Java Community Process as JSR 31. JAXB 2.0 was released under JSR 222 and becomes part of JDK since Java 6 to add support for the Web Services stack (under package javax.xml.bind). It's still part of standard JDK in Java 7 and Java 8.

In Java 9, the modules which contain Java EE technologies were deprecated for removal in a future release. The flag --add-modules=java.xml.bind can be used in In Java 9 and Java 10 to resolve these modules.

In Java 11, JAXB has been removed from JDK (together with other JEE related modules based on JEP 320) and we need to add it to the project as a separate library via Maven or Gradle.

Using JAXB in Java 8

Following examples are the usage of JAXB using Java 8, when JAXB is still part of standard JDK.

Address.java
package com.dariawan.jaxb;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
import lombok.ToString;

@ToString
@XmlType(propOrder={"type", "address1", "address2", "address3", "city"})
public class Address {

    private String type;
    private String address1;
    private String address2;
    private String address3;
    private String city;
    
    @XmlAttribute
    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getAddress1() {
        return address1;
    }

    public void setAddress1(String address1) {
        this.address1 = address1;
    }

    public String getAddress2() {
        return address2;
    }

    public void setAddress2(String address2) {
        this.address2 = address2;
    }

    public String getAddress3() {
        return address3;
    }

    public void setAddress3(String address3) {
        this.address3 = address3;
    }
    
    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }   
}
                    

PhoneNumber.java
package com.dariawan.jaxb;

import javax.xml.bind.annotation.XmlAttribute;
import lombok.ToString;

@ToString
public class PhoneNumber {

    private String type;
    private String value;
    private Boolean defaultNumber;

    @XmlAttribute
    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @XmlAttribute
    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
    
    @XmlAttribute
    public Boolean isDefaultNumber() {
        return defaultNumber;
    }

    public void setDefaultNumber(Boolean defaultNumber) {
        this.defaultNumber = defaultNumber;
    }
}
                    

Customer.java
package com.dariawan.jaxb;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import lombok.ToString;

@ToString
@XmlRootElement
@XmlType(propOrder={"id", "name", "address", "phoneNumber"})
public class Customer {

    private long id;
    private String name;
    private List<Address> address;
    private List<PhoneNumber> phoneNumber;

    public Customer() {        
        address = new ArrayList<>();
        phoneNumber = new ArrayList<>();
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Address> getAddress() {
        return address;
    }

    public void setAddress(List<Address> address) {
        this.address = address;
    }

    public List<PhoneNumber> getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(List<PhoneNumber> phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
}
                    

Our JaxbDemo will two simple things:

  • to marshal domain object (in this case Customer) to XML
  • to unmarshal XML (back) into Customer

I'm using lombok @ToString to help us to generate nice toString() output - automatically.

JaxbDemo.java
package com.dariawan.jaxb;

import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class JaxbDemo {

    public static void main(String[] args) throws JAXBException {

        // Step 1 - Create the Domain Model
        Customer customer = new Customer();
        customer.setId(1);
        customer.setName("Pablo Neruda");

        Address homeAddress = new Address();
        homeAddress.setType("home");
        homeAddress.setAddress1("Fernando Márquez de la Plata 0192");
        homeAddress.setAddress2("Barrio Bellavista");
        homeAddress.setAddress3("Providencia");
        homeAddress.setCity("Santiago");
        customer.getAddress().add(homeAddress);

        Address residentialAddress = new Address();
        residentialAddress.setType("residential");
        residentialAddress.setAddress1("Calle Ricardo de Ferrari 692");
        residentialAddress.setCity("Valparaíso");
        customer.getAddress().add(residentialAddress);
        
        PhoneNumber homeNumber = new PhoneNumber();
        homeNumber.setType("home");
        homeNumber.setValue("+56-2-2777-8741");
        homeNumber.setDefaultNumber(true);
        customer.getPhoneNumber().add(homeNumber);

        PhoneNumber workNumber = new PhoneNumber();
        workNumber.setType("home");
        workNumber.setValue("+56-2-2737-8712");
        customer.getPhoneNumber().add(workNumber);

        // Step 2 - Convert the Domain Model to XML
        JAXBContext jaxbContext = JAXBContext.newInstance(Customer.class);

        Marshaller marshaller = jaxbContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        StringWriter xmlWriter = new StringWriter();
        marshaller.marshal(customer, xmlWriter);
        System.out.println(xmlWriter.toString());
        
        // Step 3 - Convert XML back to Domain Model
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        StringReader xmlReader = new StringReader(xmlWriter.toString());
        Customer outCustomer = (Customer) unmarshaller.unmarshal(xmlReader);
        System.out.println(outCustomer);
    }
}
                    

the Marshaller will produce following output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customer> <id>1</id> <name>Pablo Neruda</name> <address type="home"> <address1>Fernando Márquez de la Plata 0192</address1> <address2>Barrio Bellavista</address2> <address3>Providencia</address3> <city>Santiago</city> </address> <address type="residential"> <address1>Calle Ricardo de Ferrari 692</address1> <city>Valparaíso</city> </address> <phoneNumber defaultNumber="true" type="home" value="+56-2-2777-8741"/> <phoneNumber type="home" value="+56-2-2737-8712"/> </customer>

which is will be used by the Unmarshaller to get Customer object:

Customer(id=1, name=Pablo Neruda, address=[Address(type=home, address1=Fernando Márquez de la Plata 0192, address2=Barrio Bellavista, address3=Providencia, city=Santiago), Address(type=residential, address1=Calle Ricardo de Ferrari 692, address2=null, address3=null, city=Valparaíso)], phoneNumber=[PhoneNumber(type=home, value=+56-2-2777-8741, defaultNumber=true), PhoneNumber(type=home, value=+56-2-2737-8712, defaultNumber=null)])

Using JAXB in Java 11

Now, back to our main topic. To use JAXB API in Java 11, we need to use separate library. Here how we "import" it using maven dependencies.

pom.xml
<project ...>
    <modelVersion>4.0.0</modelVersion>

    <!-- ... -->
    <properties>
        <!-- ... -->
        <javax.activation.version>1.2.0</javax.activation.version>
        <jaxb.api.version>2.3.0</jaxb.api.version>
        <lombok.version>1.18.6</lombok.version>
    </properties>

    <dependencies>
        <!-- ... -->

        <dependency>
            <groupId>com.sun.activation</groupId>
            <artifactId>javax.activation</artifactId>
            <version>${javax.activation.version}</version>
        </dependency>
        
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>${jaxb.api.version}</version>
        </dependency>

        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>${jaxb.api.version}</version>
        </dependency>

        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>${jaxb.api.version}</version>
        </dependency>
        
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- ... -->
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
                    

And the above codes are directly compilable in Java 11 without change anything (even single line of codes) - as long as the external library available in your project (either you are using maven, gradle or even ant).

The code run as is in Java 8, although with warning:

WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by com.sun.xml.bind.v2.runtime.reflect.opt.Injector (file:/C:/Users/Dariawan/.m2/repository/com/sun/xml/bind/jaxb-impl/2.3.0/jaxb-impl-2.3.0.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int) WARNING: Please consider reporting this to the maintainers of com.sun.xml.bind.v2.runtime.reflect.opt.Injector WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release

You need javax.activation during runtime, although not including it as dependency in maven will not fail the build process. But when you run it, you will get following error.

Exception in thread "main" java.lang.NoClassDefFoundError: javax/activation/DataSource at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl.<clinit>(RuntimeBuiltinLeafInfoImpl.java:478) at com.sun.xml.bind.v2.model.impl.RuntimeTypeInfoSetImpl.<init>(RuntimeTypeInfoSetImpl.java:63) at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.createTypeInfoSet(RuntimeModelBuilder.java:128) at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.createTypeInfoSet(RuntimeModelBuilder.java:84) at com.sun.xml.bind.v2.model.impl.ModelBuilder.<init>(ModelBuilder.java:162) at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.<init>(RuntimeModelBuilder.java:92) at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:455) at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:303) at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:139) at com.sun.xml.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1156) at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:165) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:297) at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:286) at javax.xml.bind.ContextFinder.find(ContextFinder.java:409) at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:721) at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:662) at com.dariawan.jaxb.JaxbDemo.main(JaxbDemo.java:45) Caused by: java.lang.ClassNotFoundException: javax.activation.DataSource at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ... 21 more

That's all. Now you aware that the removal of Java EE technologies from JDK standard package is not end your quest for Java EE. Third party libraries does exists as Reference Implementation (RI) and in-par with standard JDK release. Happy coding!