JAX-RSでMoshi使ったら `~but <null> is of type null` と怒られてハマった

※ この投稿は [ブログで公開した記事](http://blog.s64.jp/entry/jaxrs-moshi-type-null-response) を一部修正したものです

***

JAX-RS (Jersey) で Moshi (`moshi-kotlin`) 使ってて、雰囲気でレスポンスをreturnさせたら以下のように怒られた:


```
java.lang.IllegalArgumentException: Expected a Class, ParameterizedType, or GenericArrayType, but <null> is of type null
	at com.squareup.moshi.Types.getRawType(Types.java:167)
	at com.squareup.moshi.ClassJsonAdapter$1.createFieldBindings(ClassJsonAdapter.java:83)
	at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:75)
	at com.squareup.moshi.Moshi.adapter(Moshi.java:100)
	at com.squareup.moshi.Moshi.adapter(Moshi.java:58)
	at com.squareup.moshi.CollectionJsonAdapter.newArrayListAdapter(CollectionJsonAdapter.java:52)
	at com.squareup.moshi.CollectionJsonAdapter$1.create(CollectionJsonAdapter.java:36)
	at com.squareup.moshi.Moshi.adapter(Moshi.java:100)
	at com.squareup.moshi.ClassJsonAdapter$1.createFieldBindings(ClassJsonAdapter.java:91)
	at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:75)
	at com.squareup.moshi.Moshi.adapter(Moshi.java:100)
	at com.squareup.moshi.Moshi.adapter(Moshi.java:58)
	at com.jakewharton.moshi.rs.MoshiMessageBodyWriter.writeTo(MoshiMessageBodyWriter.java:57)
	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:266)
```

そんな`null`で埋めてる値なんて無いしなぁとか思って放置していたが、さっき気分でMoshiの実装読んでたら納得。勘が悪かった。

要約すると:

- レスポンスに `javax.ws.rs.core.Response` を使ってた
- `List<E>`のようなParameterized Typeなオブジェクトを直接entityとして食わせるとダメ

という話。  
具体的にはこういうコードのことだ:

```kotlin
// lateinit var ebean: EbeanServer

@GET
@Path("/list")
@Produces(MediaType.APPLICATION_JSON)
fun listItems(): Response {
  val items = QMyItm(ebean) // ebean-querybean
    .deletedAt.isNull()
    .findList()
  
  if(anErrorOccured()) {
    return Response.serverError().build()
  }
  
  return Response.ok(items).build() // `IllegalArgumentException`
}

// fun anErrorOccured(): Boolean  = false

```

## なぜ起きるか

これはJavaの仕様であるType Erasureによるもの。勘の良い人であれば`Response`の`entity`が `Any!` (`Object`) になっている時点で気付けたかも。  
Type Erasureについては既に語られ尽くしているため詳細な解説を避けるが、今回のケースの場合、要は

- `Response`へentityとして`myObj: List<MyItm>`が与えられる
- `Any!` (`Object`) なので、Moshiは型情報を取ろうとする。`List.class`であることがわかる
- Moshi自身の実装として、`List`の場合はParameterized Typeの`index: 0`を元に展開する必要がある
- Parameterized Typeの`index: 0`を取ろうとしたが、これは既にコンパイル時点で消去されている (Type Erasure)
- type情報が不明だと続行できないので、`IllegalArgumentException`を投げる

ということ。

## とりあえずどうすればいいか

手っ取り早く解決する方法は、

- return typeを`List<E>`のようにする (= `Response`使わない)
- `MyResponse#getItems` で取れるような形でwrapする (= Parameterized Typeでないオブジェクトにする)

など。  
恐らく`Response`を使っている理由はstatus codeなんかを適宜変えたいからだと思うのだけど、`ExceptionMapper`を実装してあげて自前のThrowableを投げればレスポンスを変えられるので、前者を取ることをお勧めする。

たとえば以下のような形になる:

```kotlin
// lateinit var ebean: EbeanServer

@GET
@Path("/list")
@Produces(MediaType.APPLICATION_JSON)
fun listItems(): List<MyItm>? {
  val items = QMyItm(ebean) // ebean-querybean
    .deletedAt.isNull()
    .findList()
  
  if(anErrorOccured()) {
    throw MySomethingWrongException()
  }
  
  return items
}

// fun anErrorOccured(): Boolean  = false

// class MySomethingWrongException : RuntimeException()

```

~~// もし`Response<T>`のような仕様だったら、こういった事は起こらなかったのかも~~