Repository: square/retrofit
Issue: RequestInterceptor.addHeader("Content-Type", "...") only crash with NullPointerException

The headers list in RequestBuilder class is lazily initialized and in the case where you add the "Content-Type" header ONLY through a RequestInterceptor the headers list isn't initialized.  This ends with a NullPointerException when executing the build() method in the RequestBuilder. 

This issue disappear as soon as you add an extra header different than "Content-Type". "Content-Type" header is treated differently in the code and if it's the only header provided this issue occurs otherwise everything is fine.

In the case where "Content-Type" is the only header you would want to intercept the workaround would be the provide the header through annotation on your interface.

Here's the stackoverflow question that sparked this issue http://stackoverflow.com/questions/24627695/retrofit-headers-nullpointerexception/


Issue comments:
Good catch. Will fix and put out a dot release!
On Jul 8, 2014 8:39 AM, "Miguel Lavigne" notifications@github.com wrote:

> The headers list in RequestBuilder class is lazily initialized and in the
> case where you add the "Content-Type" header ONLY through a
> RequestInterceptor the headers list isn't initialized. This ends with a
> NullPointerException when executing the build() method in the
> RequestBuilder.
> 
> This issue disappear as soon as you add an extra header different than
> "Content-Type". "Content-Type" header is treated differently in the code
> and if it's the only header provided this issue occurs otherwise everything
> is fine.
> 
> In the case where "Content-Type" is the only header you would want to
> intercept the workaround would be the provide the header through annotation
> on your interface.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/square/retrofit/issues/557.

That's awesome, thx!


Pull request review comments:

Patch that resolved the issue:
diff --git a/retrofit/src/main/java/retrofit/RequestBuilder.java b/retrofit/src/main/java/retrofit/RequestBuilder.java
index 777aaefabe..d04bb19152 100644
--- a/retrofit/src/main/java/retrofit/RequestBuilder.java
+++ b/retrofit/src/main/java/retrofit/RequestBuilder.java
@@ -21,6 +21,7 @@
 import java.lang.reflect.Array;
 import java.net.URLEncoder;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import retrofit.client.Header;
@@ -341,7 +342,12 @@ Request build() throws UnsupportedEncodingException {
       if (body != null) {
         body = new MimeOverridingTypedOutput(body, contentTypeHeader);
       } else {
-        headers.add(new Header("Content-Type", contentTypeHeader));
+        Header header = new Header("Content-Type", contentTypeHeader);
+        if (headers == null) {
+          headers = Arrays.asList(header);
+        } else {
+          headers.add(header);
+        }
       }
     }
 


Test patch:
diff --git a/retrofit/src/test/java/retrofit/RequestBuilderTest.java b/retrofit/src/test/java/retrofit/RequestBuilderTest.java
index 98d78f5147..5fe78daab3 100644
--- a/retrofit/src/test/java/retrofit/RequestBuilderTest.java
+++ b/retrofit/src/test/java/retrofit/RequestBuilderTest.java
@@ -764,6 +764,16 @@ public class RequestBuilderTest {
     assertThat(request.getHeaders()).contains(new Header("Content-Type", "text/not-plain"));
   }
 
+  @Test public void contentTypeInterceptorHeaderAddsHeaderWithNoBody() throws Exception {
+    Request request = new Helper() //
+        .setMethod("DELETE") //
+        .setUrl("http://example.com") //
+        .setPath("/") //
+        .addInterceptorHeader("Content-Type", "text/not-plain") //
+        .build();
+    assertThat(request.getHeaders()).contains(new Header("Content-Type", "text/not-plain"));
+  }
+
   @Test public void contentTypeParameterHeaderOverrides() throws Exception {
     Request request = new Helper() //
         .setMethod("POST") //
@@ -957,7 +967,8 @@ Request build() throws Exception {
       methodInfo.requestQuery = query;
       methodInfo.requestParamNames = paramNames.toArray(new String[paramNames.size()]);
       methodInfo.requestParamUsage = paramUsages.toArray(new ParamUsage[paramUsages.size()]);
-      methodInfo.headers = methodInfo.parseHeaders(headers.toArray(new String[headers.size()]));
+      methodInfo.headers = headers.isEmpty() ? null
+          : methodInfo.parseHeaders(headers.toArray(new String[headers.size()]));
       methodInfo.loaded = true;
 
       RequestBuilder requestBuilder = new RequestBuilder(url, methodInfo, GSON);