How to receive JSON Messages in POST body in a JAX-RS Restful web service in CXF?

Hakim Luqman picture Hakim Luqman · Jul 9, 2014 · Viewed 8.1k times · Source

I'm trying to develop a REST service using Apache-CXF, on top of JAX-RS. For starters, I have a method called test that receives a String message and int value. I want the clients to be able to pass these parameters in a POST message body. I can't seem to achieve this.

Before I paste the code here, here are some details:

  • I'm using CXF without Spring
  • It's not a web app, so I don't have the WEB-INF folder with the web.xml
  • I test the service using SoapUI and Postman (Google Chrome application)

With the following code, I get WARNING: javax.ws.rs.BadRequestException: HTTP 400 Bad Request:

DemoService.java

@WebService(targetNamespace = "http://demoservice.com")
@Path("/demoService")
public interface DemoService {
    @POST
    @Path("/test")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public String test (String message, int value);
}

DemoServiceImpl.java

public class DemoServiceImpl implements DemoService {
    @Override
    public String test(String message, int value) {
        return "test message: " + message + " value =  : " + value;
    }
}

DemoServer.java

public class DemoServer{
    public static void main(String[] args) {

        JAXRSServerFactoryBean serverFactory = new JAXRSServerFactoryBean();
        DemoService demoService = new DemoServiceImpl();
        serverFactory.setServiceBean(demoService);
        serverFactory.setAddress("http://localhost:9090");
        serverFactory.create();
    }
}

My POM.xml (minus the attributes in the root tag, everything's there)

<project ...>
    <modelVersion>4.0.0</modelVersion>
    <groupId>demo</groupId>
    <artifactId>demoService</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <cxf.version>3.0.0</cxf.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <!-- Jetty is needed if you're are not using the CXFServlet -->
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-rs-service-description</artifactId>
            <version>3.0.0-milestone1</version>
        </dependency>

    </dependencies>
</project>

Testing with {"message":"hello there!", "value":"50"} to the URL http://localhost:9090/demoService/test gave a HTTP 400 Bad Reuest.

Then I saw this question on S.O.: How to access parameters in a RESTful POST method and tried this:

added the following nested class in DemoServer.java:

    @XmlRootElement
    public static class TestRequest {
        private String message;
        private int value;

        public String getMessage() { return message; }
        public void setMessage(String message) { this.message = message; }
        public int getValue() { return value; }
        public void setValue(int value) { this.value = value; }
    }

I also modified the DemoService interface and the implementation to use this class as a parameter in the test method, although this is still ultimately not what I want to do. (just showing the implementation here, question's already getting long):

@Override
public String test(TestRequest testRequest) {
    String message = testRequest.getMessage();
    int value = testRequest.getValue();
    return "test message: " + message + " value =  : " + value;
}

And to fix this error that I got: SEVERE: No message body reader has been found for class DemoService$TestRequest, ContentType: application/json (in Postman I see error 415 - unsupported media type) I added the following dependencies (jettison and another thing) to the POM.xml:

<dependency>
    <groupId>org.codehaus.jettison</groupId>
    <artifactId>jettison</artifactId>
    <version>1.3.5</version>
</dependency>
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-rs-extension-providers</artifactId>
    <version>2.6.0</version>
</dependency>

I tested the service using the following JSON message, in a HTTP POST request:

{"testRequest":{"message":"hello there!", "value":"50"}}

This works. Though this solution where I use a TestRequest class to encapsulate the parameters works, that's not the solution I'm looking for. I want to be able to pass the two parameters in a JSON message, without having to introduce this TestRequest class (explicitly).

Questions:

  • Would this be easier to implement using Jersey?
  • I don't have a web.xml nor a WEB-INF folder, so I can't configure CXF in a cxf.xml file can I? A lot of tutorials online seem ot use a lot of XML configuration, but I don't want to deploy a framework like TomEE or Spring or Glassfish just to do that.
  • Searching online for solutions, I came across Spring Boot. Would you recommend using that, perhaps? Would that make developing web services like this easier?
  • Also, how do I get it to return the value in JSON format (or is it not supposed to do that for Strings?)

Answer

Hakim Luqman picture Hakim Luqman · Jul 24, 2014

My friend pointed me to this stack exchange question: JAX-RS Post multiple objects

and also the following documentation: http://cxf.apache.org/docs/jax-rs-and-jax-ws.html

which states:

public class CustomerService { public void doIt(String a, String b) {...}; }

By default JAX-RS may not be able to handle such methods as it requires that only a single parameter can be available in a signature that is not annotated by one of the JAX-RS annotations like @PathParam. So if a 'String a' parameter can be mapped to a @Path template variable or one of the query segments then this signature won't need to be changed :

@Path("/customers/{a}") public class CustomerService { public void doIt(@PathParam("a") String a, String b) {...}; }

So, to answer my question, NO, it cannot be done.