Skip to main content

JAXRS client on Android

Context


Not a while ago I realized that for many people on this planet including my parents, smart-phone application is the only way they understand to interact with Internet. Therefore with zero client side software development experience, I started learning Android app programming.

Only after several days of learning I found myself blocked for there doesn't seem to be a clear answer for a problem that is so essential -- how to create JAXRS client on Android application. After days of trying a working solution finally presented to me. This document talks about the solution that I found as well as, more importantly, the solutions that do not work.

It's really important to known what we are looking for before start coding. I found many discussions on internet where people provide irrelevant because they have no idea what they are looking for. A solution must satisfy all following criteria:

  1. A Java library that calls HTTP RESTful service, preferably support HTTPS, wth/without client certificate authentication. Must support interception to allow injection of additional code logic around calls.
  2. Compatible to JAXRS(JSR311). Library must provides API that returns Java proxy of JAXRS annotated interface, via which method invocation is interpreted as HTTP RESTful calls
  3. Runs on Android. To be specific, the library and all its dependencies' must fall into the subset of JavaSE API that Android platform supports.

Apache CXF


In my experience in past most RESTful applications are built on Apache CXF framework, which satisfies #1 and #2 nicely and requires minimal coding and configuration. Unfortunately it fails for #3 which is not very surprising.

Jersey based solution


This document is at the top of Google search result from keywords "JAXRS" and "Android" at the time I write this document. Steps in detail seem to imply that the author did have it work on an Android device. However this Jersey 1.x based solution does not satisfy #2. Jersey is the reference implementation of JSR311 for server side implementation, while the client side API is low-level and could be tedious for RESTful interfaces with large number of methods.

Jersey proxy client


It's kind of unbelievable that Jersey does not provide high-level client API that satisfies #2 while many of its competitors(like CXF) do. With a little research I found that there is an ongoing and young client proxy subproject under Jersey based on JAXRS 2.0(JSR-339). It's available in official Maven repository. I went ahead and added it into my Android app, which requires to upgrade all Jersey dependencies to 2.0 and JAXRS API to 2.0, at which point it started violating #3 and failed to run on Android. Worth a try though.

Finally something is working, RestEasy-mobile


This discussion might be the second item on the top of Google search result. A lot of things are covered, of which almost all are waste of time. It's amazing how much people want to jump in before they even understand the question.

You may see that the starter asked the same question, a JAXRS client that runs on Android, which is commented with "have you tried X, a simple enough JAXRS client?", "here's how to access JAXRS service in Java" and "What's API level 9?". The only thing that turns out to work after hours of trying is JBoss resteasy-mobile, someone mentioned it in discussion but no one seemed to follow up. A lesson learnt from here is, if a library does not explicitly claim it runs on Android, it usually doesn't.

Gotchas


Before the first HTTP call went through via resteasy-mobile I ran into a number of issues, which weren't problem when I built non-Android RESTful services in past.

  1. JAXB annotations are unsupported and must be removed. Even when I didn't plan to rely on them, having a useless JAXB annotation in POJO caused Andoid app to fail. This also implies that as long as interface is shared between client and server, server can not use jaxb or jackson-jaxb-json provider. I ended up using jackson-json provider.
  2. @Consumes must be defined in API. It's not required by CXF client, but required by resteasy-mobile as far as I can tell.
  3. @Produces must be defined in API probably for similar reason. In CXF when @Consumes and @Produces are undefined, library usually can intelligently match out the right data binding provider, while obviously, resteasy-mobile can not. (further confirmation needed)

Conclusion


After all the research, JBoss resteasy-mobile is the only solution I found working for me so far. I hope this document helps you avoid wasting efforts on dead ends like I did.

Solution HTTP client library JAXRS interface proxy Runs on Android
Apache CXF Yes Yes No
Jersey Client 1.x Yes No Yes
Jersey Client Proxy 2.x Yes Yes No
JBoss resteasy-mobile Yes Yes Yes
RESTlet edition for Android Yes No Yes
Resty Yes No No

Comments

Unknown said…
Thanks for the solid analysis. I had run into the same comment re resteasy-mobile you did, and your post has me going forward with it.
Jin Kwon said…
Thank your sharing this. I'm a server developer and I should guide those mobile client developers how to. Thank you. Absolutely bookmarked and tagged!!

Popular posts from this blog

Spring, Angular and other reasons I like and hate Bazel at the same time

For several weeks I've been trying to put together an Angular application served Java Spring MVC web server in Bazel. I've seen the Java, Angular combination works well in Google, and given the popularity of Java, I want get it to work with open source. How hard can it be to run arguably the best JS framework on a server in probably the most popular server-side language with  the mono-repo of planet-scale ? The rest of this post walks through the headaches and nightmares I had to get things to work but if you are just here to look for a working example, github/jiaqi/angular-on-java is all you need. https://github.com/jiaqi/angular-on-java Java web application with Appengine rule Surprisingly there isn't an official way of building Java web application in Bazel, the closest thing is the Appengine rule  and Spring MVC seems to work well with it. 3 Java classes, a JSP and an appengine.xml was all I need. At this point, the server starts well but I got "No ...

Customize IdGenerator in JPA, gap between Hibernate and JPA annotations

JPA annotation is like a subset of Hibernate annotation, this means people will find something available in Hibernate missing in JPA. One of the important missing features in JPA is customized ID generator. JPA doesn't provide an approach for developer to plug in their own IdGenerator. For example, if you want the primary key of a table to be BigInteger coming from sequence, JPA will be out of solution. Assume you don't mind the mixture of Hibernate and JPA Annotation and your JPA provider is Hibernate, which is mostly the case, a solution before JPA starts introducing new Annotation is, to replace JPA @SequenceGenerator with Hibernate @GenericGenerator. Now, let the code talk. /** * Ordinary JPA sequence. * If the Long is changed into BigInteger, * there will be runtime error complaining about the type of primary key */ @Id @Column(name = "id", precision = 12) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XyzIdGenerator") @SequenceGe...

Project Euler problem 220 - Heighway Dragon

This document goes through a Java solution for Project Euler problem 220 . If you want to achieve the pleasure of solving the unfamiliarity and you don't have a solution yet, PLEASE STOP READING UNTIL YOU FIND A SOLUTION. Problem 220 is to tell the coordinate after a given large number of steps in a Dragon Curve . The first thing came to my mind, is to DFS traverse a 50 level tree by 10^12 steps, during which it keeps track of a direction and a coordinate. Roughly estimate, this solution takes a 50 level recursion, which isn't horrible, and 10^12 switch/case calls. Written by a lazy and irresponsible Java engineer, this solution vaguely looks like: Traveler traveler = new Traveler(new Coordinate(0, 0), Direction.UP); void main() { try { traverse("Fa", 0); } catch (TerminationSignal signal) { print signal; } } void traverse(String plan, int level) { foreach(char c:plan) { switch(c) { case 'F': traveler.stepForward(); break; ca...