47

I'm trying to create a very simple REST server. I just have a test method that will return a List of Strings. Here's the code:


@GET
@Path("/test2")
public List test2(){
    List list=new Vector();
    list.add("a");
    list.add("b");
    return list;
}

It gives the following error:

SEVERE: A message body writer for Java type,
class java.util.Vector, and MIME media type,
application/octet-stream, was not found

I was hoping JAXB had a default setting for simple types like String, Integer, etc. I guess not. Here's what I imagined:


<Strings>
  <String>a</String>
  <String>b</String>
</Strings>

What's the easiest way to make this method work?

User1
  • 39,458
  • 69
  • 187
  • 265

12 Answers12

46

I used @LiorH's example and expanded it to:


@XmlRootElement(name="List")
public class JaxbList<T>{
    protected List<T> list;

    public JaxbList(){}

    public JaxbList(List<T> list){
        this.list=list;
    }

    @XmlElement(name="Item")
    public List<T> getList(){
        return list;
    }
}

Note, that it uses generics so you can use it with other classes than String. Now, the application code is simply:


    @GET
    @Path("/test2")
    public JaxbList test2(){
        List list=new Vector();
        list.add("a");
        list.add("b");
        return new JaxbList(list);
    }

Why doesn't this simple class exist in the JAXB package? Anyone see anything like it elsewhere?

User1
  • 39,458
  • 69
  • 187
  • 265
  • 1
    trying to unmarshall this with a cxf proxy led to java.lang.NullPointerException at com.sun.xml.bind.v2.runtime.reflect.Lister$CollectionLister.addToPack(Lister.java:305) – Jean Mar 08 '13 at 17:48
  • This did not work for me as well. I got an error: No writer for JaxbList. Finally I've solved it using JacksonJaxbJsonProvider. No Java code needs to be modified. Just few changes in Spring context.xml and Maven pom.xml, see http://stackoverflow.com/a/30777172/1245231 – petrsyn Jun 11 '15 at 09:46
32
@GET
@Path("/test2")
public Response test2(){
   List<String> list=new Vector<String>();
   list.add("a");
   list.add("b");

   final GenericEntity<List<String>> entity = new GenericEntity<List<String>>(list) { };
   return Response.ok().entity(entity).build();
}
Sample Code
  • 329
  • 3
  • 2
  • 7
    Still useful 2 years later :) – Oleksi May 25 '12 at 14:59
  • 5
    Still useful 3 years later :) – Jin Kwon Mar 21 '13 at 04:55
  • 2
    This does not work for me. Using Jersey 1.11 w/Glasfish 3.1.2. I get `javax.ws.rs.WebApplicationException: com.sun.jersey.api.MessageException: A message body writer for Java class java.util.Vector, and Java type java.util.List, and MIME media type application/json was not found` – NBW Jun 07 '13 at 00:49
13

In case anyone of you wants to write a list wrapper for lists containing elements of multiple classes and want to give an individual XmlElement name according to the Class type without Writing X Wrapper classes you could use the @XmlMixed annotation. By doing so JAXB names the items of the list according to the value set by the @XmlRootElement. When doing so you have to specify which classes could possibly be in the list using @XmlSeeAlso

Example:

Possible Classes in the list

@XmlRootElement(name="user")
public class User {/*...*/}

@XmlRootElement(name="entry")
public class LogEntry {/*...*/}

Wrapper class

@XmlRootElement(name="records")
@XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{

    protected List<T> records;

    public JaxbList(){}

    public JaxbList(List<T> list){
        this.records=list;
    }

    @XmlMixed 
    public List<T> getRecords(){
        return records;
    }
}

Example:

List l = new List();
l.add(new User("userA"));
l.add(new LogEntry(new UserB()));


XStream xStream = new XStream();
String result = xStream.toXML(l);

Result:

<records>
    <user>...</user>
    <entry>...</entry>
</records>

Alternatevily you could specify the XmlElement names directly inside the wrapper class using the @XmlElementRef annotation

@XmlRootElement(name="records")
@XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{

    protected List<T> records;

    public JaxbList(){}

    public JaxbList(List<T> list){
        this.records=list;
    }

    @XmlElementRefs({
        @XmlElementRef(name="item", type=Object.class),
        @XmlElementRef(name="user", type=User.class),
        @XmlElementRef(name="entry", type=LogEntry.class)
    })
    public List<T> getRecords(){
        return records;
    }
}
Zounadire
  • 1,496
  • 2
  • 18
  • 38
12

From a personal blog post, it is not necessary to create a specific JaxbList < T > object.

Assuming an object with a list of strings:

@XmlRootElement
public class ObjectWithList {

    private List<String> list;

    @XmlElementWrapper(name="MyList")
    @XmlElement
    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

}

A JAXB round trip:

public static void simpleExample() throws JAXBException {

    List<String> l = new ArrayList<String>();
    l.add("Somewhere");
    l.add("This and that");
    l.add("Something");

    // Object with list
    ObjectWithList owl = new ObjectWithList();
    owl.setList(l);

    JAXBContext jc = JAXBContext.newInstance(ObjectWithList.class);
    ObjectWithList retr = marshallUnmarshall(owl, jc);

    for (String s : retr.getList()) {
        System.out.println(s);
    } System.out.println(" ");

}

Produces the following:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectWithList>
    <MyList>
        <list>Somewhere</list>
        <list>This and that</list>
        <list>Something</list>
    </MyList>
</objectWithList>
Jérôme Verstrynge
  • 57,710
  • 92
  • 283
  • 453
11

This can be done MUCH easier using wonderful XStream library. No wrappers, no annotations.

Target XML

<Strings>
  <String>a</String>
  <String>b</String>
</Strings>

Serialization

(String alias can be avoided by using lowercase string tag, but I used OP's code)

List <String> list = new ArrayList <String>();
list.add("a");
list.add("b");

XStream xStream = new XStream();
xStream.alias("Strings", List.class);
xStream.alias("String", String.class);
String result = xStream.toXML(list);

Deserialization

Deserialization into ArrayList

XStream xStream = new XStream();
xStream.alias("Strings", ArrayList.class);
xStream.alias("String", String.class);
xStream.addImplicitArray(ArrayList.class, "elementData");
List <String> result = (List <String>)xStream.fromXML(file);

Deserialization into String[]

XStream xStream = new XStream();
xStream.alias("Strings", String[].class);
xStream.alias("String", String.class);
String[] result = (String[])xStream.fromXML(file);

Note, that XStream instance is thread-safe and can be pre-configured, shrinking code amount to one-liners.

XStream can also be used as a default serialization mechanism for JAX-RS service. Example of plugging XStream in Jersey can be found here

Community
  • 1
  • 1
Alex Abdugafarov
  • 6,112
  • 7
  • 35
  • 59
  • Can you please tell if there is an ability to use names as alias for classes by default? I have a very complex class, it contains lots of classes objects that contain classes objects that contain classes objects and so on... Is there any way to avoid thousends of calling xStream.alias(...)? Thanks – me1111 Feb 13 '13 at 16:15
  • 1
    Actually, you may serialize your class without any aliases at all, or with just alias to a root class. It will name the root tag as you've aliased (or use fully qualified class name if you didn't) and name all inner tags the same way inner fields of classes are named. Enjoy! – Alex Abdugafarov Feb 13 '13 at 16:44
  • The point is that the full name of class (including package) is unwanted for me. I want just name and not to define aliases for all used classes, because it will take a lot of space and time. Can you advice me something? – me1111 Feb 14 '13 at 07:36
  • 1
    As I said, alias only the root class and check XStream generated XML. – Alex Abdugafarov Feb 14 '13 at 12:32
8

I have encountered this pattern a few times, I found that the easiest way is to define an inner class with JaxB annotations. (anyways, you'll probably want to define the root tag name)

so your code would look something like this

@GET
@Path("/test2")
public Object test2(){
   MyResourceWrapper wrapper = new MyResourceWrapper();
   wrapper .add("a");
   wrapper .add("b");
   return wrapper ;
}

@XmlRootElement(name="MyResource")
private static class MyResourceWrapper {
       @XmlElement(name="Item")
       List<String> list=new ArrayList<String>();
       MyResourceWrapper (){}

       public void add(String s){ list.add(s);}
 }

if you work with javax.rs (jax-rs) I'd return Response object with the wrapper set as its entity

LiorH
  • 18,524
  • 17
  • 70
  • 98
  • looks like a great start. But this doesn't look thread safe. – User1 Oct 21 '09 at 20:59
  • you can remove the static modifier from the inner class if same instance of the top class can serve more than one thread. – LiorH Oct 21 '09 at 21:11
  • I'm probably doing something wrong. I'm not sure how to use the Response object you mentioned. So, I just used the code above and got: javax.ws.rs.WebApplicationException: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions XmlElementRef points to a non-existent class. Any ideas? – User1 Oct 21 '09 at 21:21
  • 1
    you are right, my mistake, trying to write code without running it. i fixed it. – LiorH Oct 21 '09 at 22:06
  • The inner class must be static, or the following error will occur: ResourceWrapper is a non-static inner class, and JAXB can't handle those. – Eddy Mar 25 '13 at 13:49
  • Still Working after 4 years !! HATS OFF !! – Altair Jun 30 '13 at 17:21
3

Finally I've solved it using JacksonJaxbJsonProvider It requires few changes in your Spring context.xml and Maven pom.xml

In your Spring context.xml add JacksonJaxbJsonProvider to the <jaxrs:server>:

<jaxrs:server id="restService" address="/resource">
    <jaxrs:providers>
        <bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
    </jaxrs:providers>
</jaxrs:server>

In your Maven pom.xml add:

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-jaxrs</artifactId>
    <version>1.9.0</version>
</dependency>
petrsyn
  • 5,054
  • 3
  • 45
  • 48
2

User1's example worked well for me. But, as a warning, it won't work with anything other than simple String/Integer types, unless you add an @XmlSeeAlso annotation:

@XmlRootElement(name = "List")
@XmlSeeAlso(MovieTicket.class)
public class MovieTicketList {
    protected List<MovieTicket> list;

This works OK, although it prevents me from using a single generic list class across my entire application. It might also explain why this seemingly obvious class doesn't exist in the JAXB package.

piepera
  • 2,033
  • 1
  • 20
  • 21
0

Make sure to add @XmlSeeAlso tag with your specific classes used inside JaxbList. It is very important else it throws HttpMessageNotWritableException

Maggy
  • 1
0

I would've saved time if I found Resteasy Jackson Provider sooner.

Just add the Resteasy Jackson Provider JAR. No entity wrappers. No XML annotations. No custom message body writers.

mstrthealias
  • 2,781
  • 2
  • 22
  • 18
0

If you are using maven in the jersey project add below in pom.xml and update project dependencies so that Jaxb is able to detect model class and convert list to Media type application XML:

<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-core</artifactId>
    <version>2.2.11</version>
</dependency>
0

For a more general solution, for JAXB-XML serialization of any top level list , which only requires 1 new class to be written, check out the solution given in this question:

Is it possible to programmatically configure JAXB?

public class Wrapper<T> {

private List<T> items = new ArrayList<T>();

@XmlAnyElement(lax=true)
public List<T> getItems() {
    return items;
}

}

//JAXBContext is thread safe and so create it in constructor or 
//setter or wherever:
... 
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, clazz);
... 

public String marshal(List<T> things, Class clazz) {

  //configure JAXB and marshaller     
  Marshaller m = jc.createMarshaller();
  m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

  //Create wrapper based on generic list of objects
  Wrapper<T> wrapper = new Wrapper<T>(things);
  JAXBElement<Wrapper> wrapperJAXBElement = new JAXBElement<Wrapper>(new QName(clazz.getSimpleName().toLowerCase()+"s"), Wrapper.class, wrapper);

  StringWriter result = new StringWriter();
  //marshal!
  m.marshal(wrapperJAXBElement, result);

  return result.toString();

}
Gonen I
  • 5,576
  • 1
  • 29
  • 60