HTTP 400 error in REST web service during POST with FormParam containing own objects (e.g. enities)

Jana picture Jana · Sep 9, 2013 · Viewed 12.9k times · Source

For an ordering process in my REST service I have to send a list of "articles" from client to server. These article objects are of a self-made entity type. I already found out that sending a list of STRING or INTEGER objects does work, sending it via @FormParam.

But as soon as I try to send a list of my own objects (even only ONE object), I always get a HTTP 400 error "Bad Request".

I tried excatly the same code like below (only the parameters of form.add() and the parameters of the server method were altered) and postet Strings, Integers and lists of Strings successfully. It only makes problems sending own object types.
Logging told me that the server method isn't reached. The process is broken somewhere before.

I also tried to get the request by using a proxy (Apache JMeter). Here it says that the parameter kunde contains the value entities.Kunde%40af8358. So I guess the object is not serialized thoroughly (or at all). But sending this kind of object from server to client in a response works fine - here the XML-serialization is no problem.

What might be the reason? Is it maybe NOT possible to send own types through POST?
(PS: The type Kundein my example is serializable and annotated with @XmlRootElement.)

Thank you in advance for your help!
Jana

Note: I'm using the SAP Netweaver AS. But until now it behaved like every other Java AS, so I don't think this will be the reason. Every other REST operation does work, even POST without own entities.

Addition: I'm using the JERSEY library.


My Code on server side:

@Path("/test")
    @POST
    @Produces(MediaType.TEXT_XML)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)    
    public String test(
        @FormParam("kunde") Kunde kunde) {    

        return "The name of the customer is: "
           +kunde.getVorname()+" "+kunde.getNachname();        
    }

My code on client side (the method is in a Session Bean):

public String  test() {

    Kunde kunde = new Kunde();
    kunde.setNachname("Müller");
    kunde.setVorname("Kurt");

    Form form = new Form();
    form.add("kunde", kunde);       

    return service
        .path("test")
        .type(MediaType.APPLICATION_FORM_URLENCODED)
        .accept(MediaType.TEXT_XML)
        .post(String.class, form);
}

where service is built like that:

com.sun.jersey.api.client.Client;
com.sun.jersey.api.client.WebResource;
com.sun.jersey.api.client.config.ClientConfig;
com.sun.jersey.api.client.config.DefaultClientConfig;

ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
service = client.resource(UriBuilder.fromUri("<service-url>").build());

Answer

Jana picture Jana · Sep 10, 2013

Due to the new information that the entity "Kunde" isn't trasmitted correctly I found a
SOLUTION:

I explicitely transformed the entity class into XML/JSON (either way is working) and put that XML/JSON as a String in the form. On the server's side I transformed the XML/JSON String back into the Entity, this worked.
(Seems like it is NOT possible to transmit an object not beeing String or Integer as it is.)

Hope this will help everyone who faces the same problem transmitting objects from client to server via REST.
(Sending a List of XML/JSON-converted objects is still to test. I'll add the result here soon.)

Greetings and thanks to chrylis for his/her comments and hints.
Jana


Here's the code for the solution, but for shortness it's only the new parts.

1) The XML solution:

For changing Entity into XML String on Client's side:

...
OutputStream out = new ByteArrayOutputStream();

JAXBContext context = JAXBContext.newInstance(Kunde.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(kunde, out);
out.close();

Form form = new Form();
form.add("entity", out.toString());
...

For transforming the XML back to an Object on the Server's side:

...
public String test(
    @FormParam("entity") String entityString) {      

    InputStream inputStream = new ByteArrayInputStream(entityString.getBytes());        
    Kunde kunde = JAXB.unmarshal(inputStream, Kunde.class);       

    return "Der Name des Kunden ist: "+kunde.getVorname()+" "+kunde.getNachname();
}


2) The JSON solution:

For changing Entity into JSON String on Client's side:

...
OutputStream out = new ByteArrayOutputStream();

ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(out, kunde);
out.close();

Form form = new Form();
form.add("entity", out.toString());
...

For transforming the JSON String back to an Object on the Server's side:

...
public String test(
    @FormParam("entity") String entityString) {      

    InputStream inputStream = new ByteArrayInputStream(entityString.getBytes());        
    Kunde kunde = new ObjectMapper().read((inputStream, Kunde.class));       

    return "Der Name des Kunden ist: "+kunde.getVorname()+" "+kunde.getNachname();
}

The classes JAXB, JAXBContext, Marshaller etc. are from package javax.xml.bind.*. The class ObjectMapper is from package org.codehaus.jackson.map.*.

PS: Because transmitting plain String now, you also could use @QueryParam. But I wouldn't recomment that, because you'd be transmitting the whole XML as a text String in the URL. Same goes for @PathParam.

I'd recommend JSON, because the format is more slender than the XML format, and being slender is the aim of REST.