Patrick Tyler
Patrick Tyler

Reputation: 191

NullPointerException when using Spring RestTemplate in Android

I'm using a Spring 3 MVC Server that is hosted on Windows Azure, and am using this to provide users with interaction both through a web browser and an Android device. All code is written in Java.

When I interact with the server through the browser, everything is OK, and when interacting with it using Android it works OK if the server is run as localhost. However, when I interact with the Azure hosted server using the Android client, I randomly (but always eventually) get a NullPointerException, so sometimes the code runs fine but eventually this will show up. Exactly where I get the NullPointer changes, but it is always during a HTTP call using RestTemplate.

Another strange thing, when running the Android app in debug, the code will run to some arbitrary point in the code (still crashing at a RestTemplate call), but the stack trace for the NullPointerException will point to a call made earlier on.

The general code I make for a call is similar to the following:

 private class ChangeDirectory extends AsyncTask< String, Void, Boolean >
{
    @Override
    protected Boolean doInBackground(String... directoryName) {

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters().add( new StringHttpMessageConverter() );

        String url = "http://" + Constants.DNS +"/ui/android/cloud/change_directory";

        restTemplate.put(url, 
                         directoryName[0]);
        return true;
    }
}

And the stack trace looks like:

09-05 11:32:05.081: E/AndroidRuntime(535): Caused by: java.lang.NullPointerException
09-05 11:32:05.081: E/AndroidRuntime(535):  at org.springframework.http.client.SimpleClientHttpResponse.getStatusCode(SimpleClientHttpResponse.java:62)
09-05 11:32:05.081: E/AndroidRuntime(535):  at org.springframework.web.client.DefaultResponseErrorHandler.hasError(DefaultResponseErrorHandler.java:46)
09-05 11:32:05.081: E/AndroidRuntime(535):  at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:476)
09-05 11:32:05.081: E/AndroidRuntime(535):  at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:438)
09-05 11:32:05.081: E/AndroidRuntime(535):  at org.springframework.web.client.RestTemplate.put(RestTemplate.java:364)
09-05 11:32:05.081: E/AndroidRuntime(535):  at com.artmaps.im.activity.HomeActivity$ChangeDirectory.doInBackground(HomeActivity.java:192)
09-05 11:32:05.081: E/AndroidRuntime(535):  at com.artmaps.im.activity.HomeActivity$ChangeDirectory.doInBackground(HomeActivity.java:1)
09-05 11:32:05.081: E/AndroidRuntime(535):  at android.os.AsyncTask$2.call(AsyncTask.java:264)
09-05 11:32:05.081: E/AndroidRuntime(535):  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
09-05 11:32:05.081: E/AndroidRuntime(535):  ... 5 more

which points to the restTemplate.put(url, directoryName[0]); call

I've tried putting in statements on the server side, and when these NullPointers are thrown it appears that the request isn't received by the server as the statements aren't reached.

Any help as to the cause of the NullPointerException would be very much appreciated as i'm pretty confused by this, especially as sometimes it works and sometimes it doesn't

EDIT: After looking around some more it appears there is a bug in the Spring for Android framework, as per https://jira.springsource.org/browse/ANDROID-102

This helps a little, but still doesn't explain why it works sometimes and only fails when interacting with the server based in Azure

Upvotes: 3

Views: 7819

Answers (4)

I solved upgrading to 1.0.1 Spring Framework and using String.format with Locale for the URL and ClientHttpFactory.

private class ChangeDirectory extends AsyncTask< String, Void, Boolean >
{
    @Override
    protected Boolean doInBackground(String... directoryName) {

        RestTemplate restTemplate = new RestTemplate(true); // default converters
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
        String url = String.format(Locale.getDefault(),"http://%s/ui/android/cloud/change_directory", Constants.DNS);

        restTemplate.put(url, directoryName[0]);
        return true;
    }
}

I hope this works for you.

Upvotes: 0

Henrique
Henrique

Reputation: 5011

I had the same issue while implementing a custom ClientHttpRequestFactory extending SimpleClientHttpRequestFactory. I would also get random NullPointerExceptions.

The solution was to extend from HttpComponentsClientHttpRequestFactory instead of SimpleClientHttpRequestFactory and perform the necessary logic in the postProcessHttpRequest(HttpUriRequest httpRequest) method.

Here's a quick example of a customized HttpComponentsClientHttpRequestFactory accesing and setting the user-agent field in the header:

public class CustomHTTPRequestFactory extends HttpComponentsClientHttpRequestFactory {

    public CustomHTTPRequestFactory() {
        super();
    }

    @Override
    protected void postProcessHttpRequest(HttpUriRequest httpRequest) {
        httpRequest.setHeader("User-Agent", "my-user-agent-string");
        super.postProcessHttpRequest(httpRequest);
    }

}

Upvotes: 1

Snicolas
Snicolas

Reputation: 38168

If you are getting this error, chances are that you have a problem with Gzip compression : Here is work around, tested on ICS & JB, but that should work everywhere :

Inspired from http://android-developers.blogspot.fr/2011/09/androids-http-clients.html

    RestTemplate restTemplate = new RestTemplate() {
        @Override
        protected ClientHttpRequest createRequest( URI url, HttpMethod method ) throws IOException {
            ClientHttpRequest request = super.createRequest( url, method );
            HttpHeaders headers = request.getHeaders();
            headers.setAcceptEncoding( ContentCodingType.GZIP );

            return request;
        }
    };

    if ( Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO ) {
        System.setProperty( "http.keepAlive", "false" );
    }

    try {
        long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
        File httpCacheDir = new File( getCacheDir(), "http" );
        Class.forName( "android.net.http.HttpResponseCache" ) //
                .getMethod( "install", File.class, long.class )//
                .invoke( null, httpCacheDir, httpCacheSize );
    } catch ( Exception httpResponseCacheNotAvailable ) {
        Ln.v( "Http cache disabled" );
    }

Where Ln is RoboGuice Log library.

Upvotes: 1

Patrick Tyler
Patrick Tyler

Reputation: 191

Thought I'd post what I found in case anybody else gets stuck on this. It turned out to be somehow related to the bug I posted in the edit to my question.

I got around this by removing the Spring RestTemplate calls and using DefaultHttpClient instead.

E.g. changing this:

RestTemplate restTemplate = new RestTemplate();
    restTemplate.getMessageConverters().add( new StringHttpMessageConverter() );

    String url = "http://" + Constants.DNS +"/ui/android/cloud/change_directory";

    restTemplate.put(url, 
                     directoryName[0]);
    return true;

to this:

String url = "http://" + Constants.DNS +"/ui/android/cloud/change_directory";
        try {

            HttpClient client = new DefaultHttpClient();
            HttpPut putRequest = new HttpPut(url);
            putRequest.setEntity(new StringEntity(directoryName[0]));
            client.execute(putRequest);

        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

Upvotes: 2

Related Questions