Accollativo Accollativo - 7 months ago 99
Java Question

Jersey client can't specify media type

I have a get call to a rest service (that works fine), and I would like to change it and use jersey.
I downloaded the last JARs from the official site and put them inside my project.
All my response thrown exceptions (except this:

String response = invocationBuilder.get(String.class);
):

javax.ws.rs.NotAcceptableException: HTTP 406 Not Acceptable
at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:1014)
at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:816)
at org.glassfish.jersey.client.JerseyInvocation.access$700(JerseyInvocation.java:92)
at org.glassfish.jersey.client.JerseyInvocation$2.call(JerseyInvocation.java:700)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:228)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:444)
at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:696)
at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:420)
at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:316)


The code:

ClientConfig clientConfig = new ClientConfig();

Client client = ClientBuilder.newClient(clientConfig);
//not so orthodox...
WebTarget webTarget = client.target(uri);//uri= https://www.foo.bar/api/v1.0/sms/sendMessage?accessKeyId=34TR&idCampaign=4&fromNumber=Test&toNumber=393281234567&message=Inserisci+la+seguente+password%3A+8c495b

Invocation.Builder invocationBuilder2 =
webTarget.request(MediaType.APPLICATION_JSON);
Invocation.Builder invocationBuilder =
webTarget.request();//------NO MEDIA TYPE SPECIFICATION
Invocation.Builder invocationBuilder3 =
webTarget.request(MediaType.APPLICATION_XML_TYPE);
Invocation.Builder invocationBuilder4 =
webTarget.request(MediaType.TEXT_XML_TYPE);
Invocation.Builder invocationBuilder5 =
webTarget.request(MediaType.TEXT_PLAIN_TYPE);
Invocation.Builder invocationBuilder6 =
webTarget.request(MediaType.APPLICATION_FORM_URLENCODED);

try {
String response2 = invocationBuilder2.get(String.class);
} catch (Exception e) {System.out.println(e);}
try {
//WORKS ONLY THIS, WITH NO MEDIA TYPE SPECIFICATION
String response = invocationBuilder.get(String.class);
} catch (Exception e) {System.out.println(e);}
try {
String response3 = invocationBuilder3.get(String.class);
} catch (Exception e) {System.out.println(e);}
try {
String response4 = invocationBuilder4.get(String.class);
} catch (Exception e) {System.out.println(e);}
try {
String response5 = invocationBuilder5.get(String.class);
} catch (Exception e) {System.out.println(e);}
try {
String response6 = invocationBuilder6.get(String.class);
} catch (Exception e) {System.out.println(e);}

//NONE OF THIS WORKS (last part of the test):
try {
SmsResponse response02 = invocationBuilder2.get(SmsResponse.class);
} catch (Exception e) {System.out.println(e);}
try {
SmsResponse response0 = invocationBuilder.get(SmsResponse.class);
} catch (Exception e) {System.out.println(e);}
try {
SmsResponse response03 = invocationBuilder3.get(SmsResponse.class);
} catch (Exception e) {System.out.println(e);}
try {
SmsResponse response04 = invocationBuilder4.get(SmsResponse.class);
} catch (Exception e) {System.out.println(e);}
try {
SmsResponse response05 = invocationBuilder5.get(SmsResponse.class);
} catch (Exception e) {System.out.println(e);}
try {
SmsResponse response06 = invocationBuilder6.get(SmsResponse.class);
} catch (Exception e) {System.out.println(e);}


The response string is a JSON:
{"status":"success","uid":"407077","numSms":1,"errorMsg":false}


But I get an exception while trying to obtain a SmsResponse object (last part of code), response0 thrown the exception below (the other cases thrown the previous 406 exception):

org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=application/json;charset=UTF-8, type=class it.sian.zfab.SmsResponse, genericType=class it.sian.zfab.SmsResponse.
at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:808)
at org.glassfish.jersey.client.JerseyInvocation.access$700(JerseyInvocation.java:92)
at org.glassfish.jersey.client.JerseyInvocation$2.call(JerseyInvocation.java:700)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:228)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:444)
at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:696)
at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:420)
at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:316)


The bean:

@XmlRootElement
public class SmsResponse implements Serializable {

private static final long serialVersionUID = 1L;

@QueryParam("uid")
private String uid;

@QueryParam("status")
private String status;

@QueryParam("errorMsg")
private String errorMsg;

@QueryParam("numSms")
private Integer numSms;

//getter and setter...
}


My old method (that works), here you can see that it's working fine with content application/json:

private <T> T restCallJson(String uri, Class<T> returnType)
{
T objectResponse = null;
try {
URL url = new URL(uri);
HttpsURLConnection connection = (HttpsURLConnection)url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Content-Type", "application/json");

InputStream inputStream = connection.getInputStream();

Gson gson = new Gson();
final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
objectResponse = gson.fromJson(reader, returnType);

connection.disconnect();
}
catch (Exception e)
{
log.error("WebServiceUtility - restCallJson: " + e);
}
return objectResponse;
}


1) Why cannot I specify the MediaType? Which one should I use?

2) Why cannot I retrieve directly the SmsResponse object?

Answer

1) Why cannot I specify the MediaType? Which one should I use?

request(type) sets the Accept: <type> header . If the server is not set up to produce the type, then the correct response to send is 404 Not Acceptable.

For example request(MediaType.APPLICATION_FORM_URLENCODED) is telling the server you want the data back in application/www-x-form-urlencoded form. If the server cannot produce that media type for the endpoint, you will get back a 406.

If the server can send JSON, then request(MediaType.APPLICATION_JSON) should work.

What I would do to debug, is instead of doing

String reponse = invocationBuilder.get(String.class);

Get the actual Response object, and check out the headers and response body.

Response response = invocationBuilder.get();
int status = response.getStatus();
String body = response.readEntity(String.class);

Apart from debugging, this will avoid the exception from the client.

What this

Invocation.Builder invocationBuilder = webTarget.request();

does is set the Accept header to the wildcard */*, meaning that the server can send whatever type it wants. Usually you don't want this, as the client needs to know the type it's getting back in order to process it. What you can do to debug, is send that wildcard request and get back the response. From there you can see the Content-Type header to see what type the server is sending back

Invocation.Builder invocationBuilder = webTarget.request();
Response response = invocationBuilder.get();
String contentType = response.getHeaderString("Content-Type");

From there you can see what type you should set for the Accept header. But looking from your second stacktrace, it seems the server is sending application/json, so there is no reason why request("application/json") shouldn't work.

2) Why cannot I retrieve directly the SmsResponse object?

You need a JSON provider that can handle deserializing from JSON to SmsResponse. For that you can use the Jackson provider. Hopefully you are using Maven. You can just add this dependency

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

You don't need to do anything else. The provider registers itself. If you're not using Maven, let me know, and I'll post all the jars you need. Just let me know what Jersey version you are using.

Aside:

You should understand the different between Content-Type and Accept. The client sends an Accept header when it wants to tell the server what type it wants back. The server may not be able to produce that type, in which case you will get a 406 Not Acceptable.

The Content-Type is used by the client to tell the server what type of data it is sending, such as with POST, but generally not for GET, as GET doesn't send any data. When the server sends back data, it always sets the Content-Type response header to tell the client what type it is getting back.

Your current use of HttpURLConnection is an incorrect usage. Instead of the Content-Type header, you should be setting the Accept header. It works fine because it just sets the wildcard Accept: */*, and Gson doesn't car about Content-Type anyway.


UPDATE

enter image description here

I just created a new Maven project, and added only the above dependency. It has transitive dependencies all all the above. But most already come with the Jersey distribution. Whatever you don't have, that's what you should look for. Mostly Jackson related jars.