JAX-RS Multiple Object Communication

I have a method;

@POST @Path("test") @Consumes(MediaType.APPLICATION_JSON) public void test(ObjectOne objectOne, ObjectTwo objectTwo) 

Now I know that I can publish one object in json format just by placing it in the body. But is it possible to make several objects? If so, how?

+63
java rest jax-rs
Apr 05 2018-11-11T00:
source share
8 answers

The answer is no .

The reason is simple: it's about the parameters that you can get in the method. They must be related to the request. Right? Thus, they must be either headers, or cookies, or query parameters, or matrix parameters, or path parameters, or a query object . (Just to tell the full story, there are additional types of parameters called context).

Now, when you get the JSON object in your request, you get it in the tag . How many bodies can a request have? One and only. This way you can get only one JSON object.

+57
Apr 6 2018-11-11T00:
source share

You cannot use your method like this, as Tarlog correctly points out.

However, you can do this:

 @POST @Path("test") @Consumes(MediaType.APPLICATION_JSON) public void test(List<ObjectOne> objects) 

or that:

 @POST @Path("test") @Consumes(MediaType.APPLICATION_JSON) public void test(BeanWithObjectOneAndObjectTwo containerObject) 

In addition, you can always combine your method with GET parameters:

 @POST @Path("test") @Consumes(MediaType.APPLICATION_JSON) public void test(List<ObjectOne> objects, @QueryParam("objectTwoId") long objectTwoId) 
+79
Jan 08 '13 at 8:10
source share

If we look at what the OP is trying to do, he / she is trying to publish two (possibly unrelated) JSON objects. First, any solution to try to send one part as a body, and one part as some other parameter, IMO, are terrible decisions. POST data must go in the body. It is not right to do something just because it works. Some workarounds may violate the basic principles of REST.

I see some solutions

  1. Use the app / x-www -F orm-urlencoded
  2. Use Multipart
  3. Just wrap them in one parent

1. Use application / x-www -F orm-urlencoded

Another option is to simply use application/x-www-Form-urlencoded . In fact, we can have JSON values. For exam

 curl -v http://localhost:8080/api/model \ -d 'one={"modelOne":"helloone"}' \ -d 'two={"modelTwo":"hellotwo"}' public class ModelOne { public String modelOne; } public class ModelTwo { public String modelTwo; } @Path("model") public class ModelResource { @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public String post(@FormParam("one") ModelOne modelOne, @FormParam("two") ModelTwo modelTwo) { return modelOne.modelOne + ":" + modelTwo.modelTwo; } } 

The only thing we need for this to work is ParamConverterProvider for this to work. Below is an example that was implemented by Michal Gajos from the Jersey team (located here with an explanation ).

 @Provider public class JacksonJsonParamConverterProvider implements ParamConverterProvider { @Context private Providers providers; @Override public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType, final Annotation[] annotations) { // Check whether we can convert the given type with Jackson. final MessageBodyReader<T> mbr = providers.getMessageBodyReader(rawType, genericType, annotations, MediaType.APPLICATION_JSON_TYPE); if (mbr == null || !mbr.isReadable(rawType, genericType, annotations, MediaType.APPLICATION_JSON_TYPE)) { return null; } // Obtain custom ObjectMapper for special handling. final ContextResolver<ObjectMapper> contextResolver = providers .getContextResolver(ObjectMapper.class, MediaType.APPLICATION_JSON_TYPE); final ObjectMapper mapper = contextResolver != null ? contextResolver.getContext(rawType) : new ObjectMapper(); // Create ParamConverter. return new ParamConverter<T>() { @Override public T fromString(final String value) { try { return mapper.reader(rawType).readValue(value); } catch (IOException e) { throw new ProcessingException(e); } } @Override public String toString(final T value) { try { return mapper.writer().writeValueAsString(value); } catch (JsonProcessingException e) { throw new ProcessingException(e); } } }; } } 

If you are not scanning resources and providers, simply register this provider, and the above example should work.

2. Use Multipart

One solution that no one has mentioned is to use multipart . This allows us to send arbitrary parts in the request. Since each request can have only one object body, the workaround is to bypass, since it allows you to have different parts (with its own types of content) as part of the object body.

Here is an example of using a jersey (see white paper here )

dependence

 <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-multipart</artifactId> <version>${jersey-2.x.version}</version> </dependency> 

Register MultipartFeature

 import javax.ws.rs.ApplicationPath; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.server.ResourceConfig; @ApplicationPath("/api") public class JerseyApplication extends ResourceConfig { public JerseyApplication() { packages("stackoverflow.jersey"); register(MultiPartFeature.class); } } 

Resource class

 import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.glassfish.jersey.media.multipart.FormDataParam; @Path("foobar") public class MultipartResource { @POST @Consumes(MediaType.MULTIPART_FORM_DATA) public Response postFooBar(@FormDataParam("foo") Foo foo, @FormDataParam("bar") Bar bar) { String response = foo.foo + "; " + bar.bar; return Response.ok(response).build(); } public static class Foo { public String foo; } public static class Bar { public String bar; } } 

Now, the complexity of some clients is that there is no way to set the Content-Type each part of the body, which is necessary for the above. The multiuser provider will search for a message body reader, depending on the type of each part. If application/json not set for it or the type for which Foo or Bar has a reader, this will fail. We will use JSON here. There is no additional configuration besides having a reader. I will use Jackson. Based on the dependency below, no other configuration is required, as the provider will be discovered by scanning the classpath.

 <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> <version>${jersey-2.x.version}</version> </dependency> 

Now the test. I will use cURL . You can see that I explicitly set the Content-Type for each part with type . -F means the other part. (See at the bottom of the post for an idea of ​​what the request body actually looks like.)

curl -v -X POST\-H "Content-Type:multipart/form-data"\-F "bar={\"bar\":\"BarBar\"};type=application/json"\-F "foo={\"foo\":\"FooFoo\"};type=application/json"\http://localhost:8080/api/foobar
Result: FooFoo; BarBar FooFoo; BarBar FooFoo; BarBar FooFoo; BarBar

The result is exactly as we expected. If you look at the resource method, all we do is return this line foo.foo + "; " + bar.bar , assembled from two JSON objects.

You can see some examples of using various JAX-RS clients from the links below. You will also see some server side example from these various JAX-RS implementations. Each link should have somewhere a link to the official documentation for this implementation.

  • Jersey example
  • Restysi example
  • CXF example

There are other implementations of JAX-RS, but you will need to find documentation for them yourself. The above three are the only ones with which I have experience.

As for Javascript clients, most of the examples that I see (for example, some of them include setting the Content-Type to undefined / false (using FormData ), allowing the browser to handle it. But this will not work for us, as the Browser will not set a Content-Type for each part, and the default type is text/plain .

I am sure that there are libraries that allow you to set the type for each part, but just to show you how to do it manually, I will publish an example (I will help a bit here . I will use Angular, but the main work is to create an entity body will be plain old javascript.

 <!DOCTYPE html> <html ng-app="multipartApp"> <head> <script src="js/libs/angular.js/angular.js"></script> <script> angular.module("multipartApp", []) .controller("defaultCtrl", function($scope, $http) { $scope.sendData = function() { var foo = JSON.stringify({foo: "FooFoo"}); var bar = JSON.stringify({bar: "BarBar"}); var boundary = Math.random().toString().substr(2); var header = "multipart/form-data; charset=utf-8; boundary=" + boundary; $http({ url: "/api/foobar", headers: { "Content-Type": header }, data: createRequest(foo, bar, boundary), method: "POST" }).then(function(response) { $scope.result = response.data; }); }; function createRequest(foo, bar, boundary) { var multipart = ""; multipart += "--" + boundary + "\r\nContent-Disposition: form-data; name=foo" + "\r\nContent-type: application/json" + "\r\n\r\n" + foo + "\r\n"; multipart += "--" + boundary + "\r\nContent-Disposition: form-data; name=bar" + "\r\nContent-type: application/json" + "\r\n\r\n" + bar + "\r\n"; multipart += "--" + boundary + "--\r\n"; return multipart; } }); </script> </head> <body> <div ng-controller="defaultCtrl"> <button ng-click="sendData()">Send</button> <p>{{result}}</p> </div> </body> </html> 

The interesting part is the createRequest function. Here we build the component by setting the Content-Type each part to application/json and concatenating the string objects foo and bar for each part. If you are not familiar with the multi-part format, see here for more information . Another interesting part is the headline. We set it to multipart/form-data .

Below is the result. In Angular, I just used the result to display in HTML, with $scope.result = response.data . The button you see was just for making a request. You will also see request data in firebug

enter image description here

3. Just wrap them in one parent

This option should be understood, as others have already mentioned.

+31
Mar 27 '15 at 18:23
source share

The following approach is usually applied in such cases:

 TransferObject { ObjectOne objectOne; ObjectTwo objectTwo; //getters/setters } @POST @Path("test") @Consumes(MediaType.APPLICATION_JSON) public void test(TransferObject object){ // object.getObejctOne().... } 
+6
Mar 01 '15 at 19:28
source share

You cannot put two separate objects in one POST call, as explained by Tarlog.

In any case, you can create a third container object that contains the first two objects and pass the one that is inside the POS call.

+4
Jul 16 2018-12-12T00:
source share

I also ran into this problem. Perhaps this will help.

 @POST @Path("/{par}") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Object centralService(@PathParam("par") String operation, Object requestEntity) throws JSONException { ObjectMapper objectMapper=new ObjectMapper(); Cars cars = new Cars(); Seller seller = new Seller(); String someThingElse; HashMap<String, Object> mapper = new HashMap<>(); //Diamond ))) mapper = (HashMap<String, Object>) requestEntity; cars=objectMapper.convertValue(mapper.get("cars"), Cars.class); seller=objectMapper.convertValue(mapper.get("seller"), Seller.class); someThingElse=objectMapper.convertValue(mapper.get("someThingElse"), String.class); System.out.println("Cars Data "+cars.toString()); System.out.println("Sellers Data "+seller.toString()); System.out.println("SomeThingElse "+someThingElse); if (operation.equals("search")) { System.out.println("Searching"); } else if (operation.equals("insertNewData")) { System.out.println("Inserting New Data"); } else if (operation.equals("buyCar")) { System.out.println("Buying new Car"); } JSONObject json=new JSONObject(); json.put("result","Works Fine!!!"); return json.toString(); } *******CARS POJO********@XmlRootElement for Mapping Object to XML or JSON*** @XmlRootElement public class Cars { private int id; private String brand; private String model; private String body_type; private String fuel; private String engine_volume; private String horsepower; private String transmission; private String drive; private String status; private String mileage; private String price; private String description; private String picture; private String fk_seller_oid; } // Setters and Getters Omitted *******SELLER POJO********@XmlRootElement for Mapping Object to XML or JSON*** @XmlRootElement public class Seller { private int id; private String name; private String surname; private String phone; private String email; private String country; private String city; private String paste_date; }//Setters and Getters omitted too *********************FRONT END Looks Like This****************** $(function(){ $('#post').on('click',function(){ console.log('Begins'); $.ajax({ type:'POST', url: '/ENGINE/cars/test', contentType: "application/json; charset=utf-8", dataType: "json", data:complexObject(), success: function(data){ console.log('Sended and returned'+JSON.stringify(data)); }, error: function(err){ console.log('Error'); console.log("AJAX error in request: " + JSON.stringify(err, null, 2)); } }); //-- END of Ajax console.log('Ends POST'); console.log(formToJSON()); }); // -- END of click function POST function complexObject(){ return JSON.stringify({ "cars":{"id":"1234","brand":"Mercedes","model":"S class","body_type":"Sedan","fuel":"Benzoline","engine_volume":"6.5", "horsepower":"1600","transmission":"Automat","drive":"Full PLag","status":"new","mileage":"7.00","price":"15000", "description":"new car and very nice car","picture":"mercedes.jpg","fk_seller_oid":"1234444"}, "seller":{ "id":"234","name":"Djonotan","surname":"Klinton","phone":"+994707702747","email":"email@gmail.com", "country":"Azeribaijan","city":"Baku","paste_date":"20150327"}, "someThingElse":"String type of element" }); } //-- END of Complex Object });// -- END of JQuery - Ajax 
+1
Mar 27 '15 at 14:12
source share

This can be done if the declared POST method accepts an array of objects. Example like this

 T[] create(@RequestBody T[] objects) { for( T object : objects ) { service.create(object); } } 
0
Nov 09 '16 at 19:02
source share

Change @Consumes (MediaType.APPLICATION_JSON) to @Consumes ({MediaType.APPLICATION_FORM_URLENCODED}). Then you can put several objects in the body

0
Jan 21 '19 at 8:25
source share



All Articles