1

New to scala futures I try to call a web service like

wsClient.url(baseUrl + url).withHeaders("Content-Type" -> "application/json").post(dataForR).flatMap(parseOutlierResponse)

using ply-ws library

I validate & map the response as follows https://gist.github.com/geoHeil/943a18d43279762ad4cdfe9aa2e40770

The main thing is:

Await.result(callAMethodCallingTheFirstSnippet, 5.minutes)

Strangely this works just fine in the repl. However if run via sbt run I get a NullPointer Exception. I already verified the JSON response manually. It validates like a breeze. Even the mapping works great. However, there must be a problem with the futures I am using. But I am not sure what is wrong. It seems like the flatMap method is called before there already is a result.

Interestingly if I do not await the result there is no null-pointer exception, but the parsed result is displayed correctly (however, the program does not exit). But there, where I really use this code, I somehow need to await the successful completion to further deal with it.

Below you will find an illustration of the problem

How can the response body be null?

Georg Heiler
  • 16,916
  • 36
  • 162
  • 292
  • Sounds like there is no response JSON. Did you try printing out the response body? – joesan May 21 '16 at 05:12
  • As mentioned above when run in the REPL it really works and the `Await.result(result, 10.minutes)` maps the response just fine. So yes there is a response when printed like `wsClient.url(baseUrl + url).withHeaders("Content-Type" -> "application/json").post(dataForR).map(response => println(response.body))` – Georg Heiler May 21 '16 at 05:37
  • I used the printed response.body to obtain the data. Then again the mapping works great `val dataP = Json.parse(manuallyExtractedData).validate[Seq[OutlierPortal]].fold( error => { println(s"Unable to parse response: $error") Future.failed(new RuntimeException("parse-json-failed")) }, outlierResponse => Future.successful(outlierResponse.map { _.toMap }) ) dataP.foreach(println)` – Georg Heiler May 21 '16 at 05:52
  • @sparkr please see the minimum example and the latest screenshot. – Georg Heiler May 22 '16 at 05:52

2 Answers2

0

I do not see any major concern with your code! I made a small test with the following code bit and it seems to be working perfectly, both in the REPL and when using sbt run:

    WS.clientUrl(s"http://$hostName/api/put").withHeaders(jsonHeaders: _*).post(body).map { r =>
      if (r.status >= 400)
        logger.warn(s"Invalid http response status: ${r.status} \n ${r.body}")
      else
        logger.debug(s"Successfully persisted data. Http response ${r.status}")
    }
joesan
  • 13,963
  • 27
  • 95
  • 232
  • indeed your code prints the response. However if the mapping like `r.json.validate[Seq[OutlierPortal]].fold( error => { println(s"Unable to parse response: $error") (new RuntimeException("parse-json-failed")) }, outlierResponse => outlierResponse.map { _.toMap }` is added I get the null pointer from above. – Georg Heiler May 22 '16 at 12:33
0

After more and more debugging I found that some implicits were in the wrong scope and the order of dependent case-classes was wrong. After moving them into the correct scope (the method performing the request) the null-pointer exception is fixed.

I could only find the "real" error after changing from flatmap to map which I find very strange. However, now both methods work fine.

Georg Heiler
  • 16,916
  • 36
  • 162
  • 292
  • Just wondering, why do you wrap the results in `parseOutlierResponse` using Future.successful. This looks superfluous, you can just throw the exceptions and `map` the `post()`. – Manuel Bernhardt May 25 '16 at 21:05
  • Well starting out with futures I based my approach on https://github.com/studiodev/Mocky/blob/master/app/services/GithubRepository.scala and they use it like that. But I understand your point now. – Georg Heiler May 27 '16 at 08:08