Java 11 HttpClient, Gson, Gradle, and Modularization

This post describes a simple system that uses the new HttpClient class that comes with Java 11 to access a RESTful web service. After demonstrating basic functionality, changes are made to parse the results using Gson, and even modularize the code and have it work with Gradle. The goal is to provide an example for anyone who wants to do similar tasks with their own systems.

Java 11 is a Long Term Support (LTS) release for the Java standard edition, meaning bug fixes and security patches will be available for at least the next three years (see the extensive Java Is Still Free post at for details about the future licensing arrangements). Java 11 contains very few new features, however — many more were added in Java 9 and 10. Most of the industry did not upgrade during those releases, however. Now that another LTS release is available, that will likely change.

One of the few real additions to Java 11 is the HttpClient API, as described in JEP 321. This takes a related incubator project from Java 9 and standardizes it for Java 11. This blog post will not be an exhaustive demonstration of its capabilities, but it will show both a synchronous and asynchronous example of how to use it, along with JUnit 5 test cases and a Gradle build file, and even modularize the code.

The web service is provided by Open Notify, which is an open source project that provides a few links to data from NASA. In this case, the example uses the “number of people in space” link at http://api.open-notify.org/astros.json. The current response to that link is:

{"message": "success", 
 "people": [
    {"craft": "ISS", "name": "Oleg Artemyev"}, 
    {"craft": "ISS", "name": "Andrew Feustel"}, 
    {"craft": "ISS", "name": "Richard Arnold"}, 
    {"craft": "ISS", "name": "Sergey Prokopyev"}, 
    {"craft": "ISS", "name": "Alexander Gerst"}, 
    {"craft": "ISS", "name": "Serena Aunon-Chancellor"}], 
 "number": 6}

The root object contains attributes message, number, and people, the last of which is a collection of objects, each of which contains a name and a craft. As the response shows, at the moment there are six people aboard the International Space Station.

Even though the plan is to map that response to Java classes, at first the example will simply download the JSON data and write it as a string to the console.

The HttpClient class is in the java.net.http package. You create one using a builder, as in:

HttpClient client = HttpClient.newBuilder()
                              .version(HttpClient.Version.HTTP_2)
                              .connectTimeout(Duration.ofSeconds(2))
                              .build();

Two of the many configuration options are shown: using HTTP/2 (which is actually the default) and setting a two-second connection timeout period. Other options, like setting a proxy, following redirects, or using an authenticator are shown in the (early access) JavaDocs.

Requests can be sent synchronously or asynchronously as desired, using the send or sendAsync methods. Either way, the first argument to those methods is an instance of HttpRequest, which is also created using a builder:

String astroUrl = "http://api.open-notify.org/astros.json";
HttpRequest.newBuilder()
           .uri(URI.create(astroUrl))
           .GET()
           .build();

Sending a synchronous request also requires a “body handler”, which comes from the HttpResponse.BodyHandlers inner class factory methods. For this case, it’s easiest just to use the ofString method:

HttpResponse response = client.send(
    buildRequest(),
    HttpResponse.BodyHandlers.ofString());
String output = response.body();

From the HttpResponse you can get the headers, the body (as shown), the status code, or several other properties.

An asynchronous request is sent using the sendAsync method, which returns a CompletableFuture. Extracting the result can be done with code like:

String output = client.sendAsync(buildRequest(),
                                 HttpResponse.BodyHandlers.ofString())
                      .thenApply(HttpResponse::body)
                      .get();

The thenApply method from CompletableFuture takes a java.util.function.Function argument that in this case maps the response into a String containing the body. The subsequent get method blocks until the request and conversion has completed.

To summarize, here is the initial version of the class called AstroClient:

package astro;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.ExecutionException;

public class AstroClient {
    private HttpClient client = HttpClient.newBuilder()
                                          .version(HttpClient.Version.HTTP_2)
                                          .connectTimeout(Duration.ofSeconds(2))
                                          .build();

    private HttpRequest buildRequest() {
        String astroUrl = "http://api.open-notify.org/astros.json";
        return HttpRequest.newBuilder()
                          .uri(URI.create(astroUrl))
                          .GET()
                          .build();
    }

    public String getSync() throws IOException, InterruptedException {
        HttpResponse response = client.send(
                buildRequest(),
                HttpResponse.BodyHandlers.ofString());
        return response.body();
    }


    public String getAsync() throws ExecutionException, InterruptedException {
        return client.sendAsync(buildRequest(),
                                HttpResponse.BodyHandlers.ofString())
                     .thenApply(HttpResponse::body)
                     .get();
    }

}

Ultimately, the tests will use some cool new methods from JUnit 5, but for the moment if you just invoke the getSync and getAsync methods and log the output using a java.util.logging.Logger you get something like:


Sep 22, 2018 12:45:54 PM astro.AstroClientTest getSync
INFO: {"message": "success", "people": [{"craft": "ISS", "name": "Oleg Artemyev"}, {"craft": "ISS", "name": "Andrew Feustel"}, {"craft": "ISS", "name": "Richard Arnold"}, {"craft": "ISS", "name": "Sergey Prokopyev"}, {"craft": "ISS", "name": "Alexander Gerst"}, {"craft": "ISS", "name": "Serena Aunon-Chancellor"}], "number": 6}

Sep 22, 2018 12:45:54 PM astro.AstroClientTest getAsync
INFO: {"message": "success", "people": [{"craft": "ISS", "name": "Oleg Artemyev"}, {"craft": "ISS", "name": "Andrew Feustel"}, {"craft": "ISS", "name": "Richard Arnold"}, {"craft": "ISS", "name": "Sergey Prokopyev"}, {"craft": "ISS", "name": "Alexander Gerst"}, {"craft": "ISS", "name": "Serena Aunon-Chancellor"}], "number": 6}

Obviously we can do better if we parse the response. To do that, add the Google Gson dependency and use it to convert the JSON response to a pair of classes. The classes that map to the JSON structure are Assignment:

package astro.json;

public class Assignment {
    private String name;
    private String craft;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCraft() {
        return craft;
    }

    public void setCraft(String craft) {
        this.craft = craft;
    }
}

which includes fields for the name and craft, and AstroResponse:

package astro.json;

import java.util.List;

public class AstroResponse {
    private String message;
    private Integer number;
    private List people;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Integer getNumber() {
        return number;
    }

    public void setNumber(Integer number) {
        this.number = number;
    }

    public List getPeople() {
        return people;
    }

    public void setPeople(List people) {
        this.people = people;
    }
}

These POJOs could be simplified by using

  • Lombok annotations
  • Groovy POGOs, preferably with the @Canonical annotation
  • Kotlin data classes

among other ways. Any of those would cut the POJOs down to just their attributes, but these are small enough to be readable even without that.

The changes to the AstroClient class are to add a method that converts the String response to an instance of AstroResponse:

package astro;

import astro.json.AstroResponse;
import com.google.gson.Gson;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.ExecutionException;

public class AstroClient {
    private HttpClient client = ...

    private Gson gson = new Gson();

    private HttpRequest buildRequest() {
        // ... as before ...
    }

    public AstroResponse getSync() throws IOException, InterruptedException {
        HttpResponse response = client.send(
                buildRequest(),
                HttpResponse.BodyHandlers.ofString());
        return getResponse(response.body());
    }


    public AstroResponse getAsync() throws ExecutionException, InterruptedException {
        String json = client.sendAsync(buildRequest(),
                                HttpResponse.BodyHandlers.ofString())
                     .thenApply(HttpResponse::body)
                     .get();
        return getResponse(json);
    }


    private AstroResponse getResponse(String json) {
        return gson.fromJson(json, AstroResponse.class);
    }
}

Now that the methods return an AstroResponse, the tests can get more interesting. Here is the complete AstroClientTest class:

package astro;

import astro.json.Assignment;
import astro.json.AstroResponse;
import org.junit.jupiter.api.Test;

import java.time.Duration;
import java.util.List;
import java.util.logging.Logger;

import static org.junit.jupiter.api.Assertions.*;

class AstroClientTest {
    private Logger logger = Logger.getLogger(AstroClientTest.class.getName());

    private AstroClient client = new AstroClient();

    @Test
    void getSync() {
        AstroResponse response = assertTimeoutPreemptively(
                Duration.ofSeconds(2),
                () -> client.getSync());

        int num = response.getNumber();
        List assignments = response.getPeople();

        assertEquals("success", response.getMessage());
        assertEquals(num, assignments.size());
        assignments.forEach(assignment ->
           assertAll(() -> assertTrue(assignment.getName().length() > 0),
                     () -> assertTrue(assignment.getCraft().length() > 0)));

        logResponse(num, assignments);
    }

    @Test
    void getAsync() {
        AstroResponse response = assertTimeoutPreemptively(
                Duration.ofSeconds(2),
                () -> client.getAsync());

        int num = response.getNumber();
        List assignments = response.getPeople();

        assertEquals("success", response.getMessage());
        assertEquals(num, assignments.size());

        logResponse(num, assignments);
    }

    private void logResponse(int num, List assignments) {
        logger.info(String.format("There are %d people in space", num));
        assignments.forEach(person -> logger.info(
                () -> String.format("%s aboard %s",
                                    person.getName(),
                                    person.getCraft())));
    }
}

The first test uses the neat assertTimeoutPreemtively method. That causes JUnit to spawn a separate thread for the test, which it interrupts if the test does not complete within the specified Duration. The second argument is a ThrowingSupplier, which is like a regular java.util.function.Supplier except that its single abstract method, get, is declared to throw Throwable.

After checking that the message field is “success” and that the size of the Assignment collection (called people to match the JSON attribute) has the right size, each individual Assignment is checked to ensure its name and craft fields are not just empty strings. The assertAll method in JUnit 5 takes a variable argument list of Executable instances, which are like Runnable instances but can throw Exception. The nice thing about assertAll is that all of its Executable arguments will be evaluated, even if some of them fail. The asynchronous test does the same thing.

The private logResponse method just uses the logger to write out how many people are in space and their names and crafts:


Sep 22, 2018 4:58:27 PM astro.AstroClientTest logResponse
INFO: There are 6 people in space
Sep 22, 2018 4:58:27 PM astro.AstroClientTest lambda$logResponse$6
INFO: Oleg Artemyev aboard ISS
Sep 22, 2018 4:58:27 PM astro.AstroClientTest lambda$logResponse$6
INFO: Andrew Feustel aboard ISS
Sep 22, 2018 4:58:27 PM astro.AstroClientTest lambda$logResponse$6
INFO: Richard Arnold aboard ISS
Sep 22, 2018 4:58:27 PM astro.AstroClientTest lambda$logResponse$6
INFO: Sergey Prokopyev aboard ISS
Sep 22, 2018 4:58:27 PM astro.AstroClientTest lambda$logResponse$6
INFO: Alexander Gerst aboard ISS
Sep 22, 2018 4:58:27 PM astro.AstroClientTest lambda$logResponse$6
INFO: Serena Aunon-Chancellor aboard ISS
Sep 22, 2018 4:58:28 PM astro.AstroClientTest logResponse

INFO: There are 6 people in space
Sep 22, 2018 4:58:28 PM astro.AstroClientTest lambda$logResponse$6
INFO: Oleg Artemyev aboard ISS
Sep 22, 2018 4:58:28 PM astro.AstroClientTest lambda$logResponse$6
INFO: Andrew Feustel aboard ISS
Sep 22, 2018 4:58:28 PM astro.AstroClientTest lambda$logResponse$6
INFO: Richard Arnold aboard ISS
Sep 22, 2018 4:58:28 PM astro.AstroClientTest lambda$logResponse$6
INFO: Sergey Prokopyev aboard ISS
Sep 22, 2018 4:58:28 PM astro.AstroClientTest lambda$logResponse$6
INFO: Alexander Gerst aboard ISS
Sep 22, 2018 4:58:28 PM astro.AstroClientTest lambda$logResponse$6
INFO: Serena Aunon-Chancellor aboard ISS

To cap it all off, here is the Gradle build file (using the current version of Gradle, 4.10.2):

plugins {
    id 'java'
}

group 'kousenit.com'
version '1.0'

sourceCompatibility = 11

repositories {
    jcenter()
}

dependencies {
    implementation 'com.google.code.gson:gson:2.8.5'
    
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
}

test {
    useJUnitPlatform()
}

There’s nothing terribly surprising in that file to experienced Gradle users, though as a reminder the useJUnitPlatform method was added to Gradle to make it easier to run JUnit 5 tests.

Honestly, this is good enough. There’s no particular reason to go on and modularize this code; it all works fine on Java 11. However, just to show the kinds of changes needed if you’re foolish enough to add a module-info.java file to the class path, here’s what happens:

First, you need to add the necessary requires statements to the module-info.java file, which resides in the src/main/java directory:

module http.client.demo.main {
    requires java.net.http;
    requires gson;
}

The module name can be anything, but the same “reverse dns” pattern favored for packages is recommended. The name here is the one auto-generated by IntelliJ IDEA if you ask for a new module-info descriptor.

The HttpClient API was in an incubator module in Java 9, which was called jdk.incubator.httpclient. Now that it is standardized, it’s now in the java.net.http module, so that’s the module needed.

The hard part was figuring out how to refer to Gson. Since that API has not yet been modularized, the associated jar files wind up in the “unnamed” module and you need to refer to them by name rather than some official module name. Enough googling (no pun intended) and eventually you’ll find that the right name to use is “gson”. Of course, that’s fragile and will no doubt change at some point in the future.

If you’ve played with modules in Java 9 or 10, you’ll notice something conspicuous by its absence. Prior to Java 11, the Java util logger (the one in the java.util.logging package) was part of a separate module in the JDK called java.logging. In Java 11 that was moved back into java.base, so no additional requires statement is necessary.

The other necessary change is inside the Gradle build file. You have to tell Gradle to use the module path rather than the class path for compilation. Based on the Gradle Guide called Building Java 9 Modules, the following additional block was necessary:

compileJava {
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
        ]
        classpath = files()
    }
}

This code adds a doFirst block to the compileJava task, which is a pre-processing step. It adds the --module-path compiler argument and assigns it to the jars on the classpath. Then the block sets the classpath variable to an empty collection of files.

The Gradle Guide also suggests that the compileTestJava and test tasks also need to be modified, but those turned out not to be necessary.

(Those are the only needed changes, which, looking back on them, was a bit disappointing given how much time it took to figure them out. But that’s no doubt a good thing.:))

The complete code is hosted in GitHub at https://github.com/kousen/http-client-demo . The master branch has everything, including the modularization.

As a reminder, I have a book that discusses the new features added to Java 8 (and much of Java 9) called Modern Java Recipes. The GitHub repositories for the book are https://github.com/kousen/java_8_recipes and https://github.com/kousen/java_9_recipes, both of which now build correctly under Java 11.

Groovy Weather: A New Groovy Example at Java.net

One of the main goals of Making Java Groovy is to show Java developers how much Groovy can make their lives easier. To that end, I just published a blog post (through Manning’s account) over a Java.net entitled, Groovy Weather: POGOs, Gson, and Open Weather. The blog post comes with a coupon code for 45% off the book. 🙂

(Spoiler: it’s “kousenjn“, but if you’re going to use it at least drop by the blog link.)

Another spoiler: it’s freakin’ cold outside. That’s partly what my blog post is about — consuming JSON data from Open Weather Map and displaying it at the console. That’s not terribly difficult, but the real value added comes from using Google’s Gson parser to convert the JSON objects into Groovy.

If you’re new to Groovy, the blog post shows a lot of detail. Regular readers of my blog, however, probably are at least comfortable with the language, so I thought I’d summarize the good parts here.

First, I need to show the JSON data, so I know what to map to.

(Er, “to what to map”? Ugh. Winston Churchill was once criticized for ending a sentence with a preposition. His reply was, “Madam, that is nonsense up with which I will not put.” That’s one of those possibly apocryphal stories that I don’t want to try to verify because I like it too much.)

Here’s the trivial Groovy expression to download the data:

import groovy.json.*

String url = 'http://api.openweathermap.org/data/2.5/weather?q=marlborough,ct'
String jsonTxt = url.toURL().text
println JsonOutput.prettyPrint(jsonTxt)

I’m using the static prettyPrint method of JsonOutput to make viewing the results easier. As a minor complaint, I have to mention that every time I use prettyPrint, I’m surprised it doesn’t actually print. It just formats the JSON data. I still need to print the result. That seems rather misleading to me.

Anyway, here’s the result:

{
    "coord": {
        "lon": -72.46,
        "lat": 41.63
    },
    "sys": {
        "message": 0.193,
        "country": "United States of America",
        "sunrise": 1389096985,
        "sunset": 1389130583
    },
    "weather": [
        {
            "id": 800,
            "main": "Clear",
            "description": "Sky is Clear",
            "icon": "01d"
        }
    ],
    "base": "gdps stations",
    "main": {
        "temp": 260.41,
        "humidity": 33,
        "pressure": 1025,
        "temp_min": 258.71,
        "temp_max": 262.15
    },
    "wind": {
        "speed": 1.54,
        "deg": 0
    },
    "clouds": {
        "all": 0
    },
    "dt": 1389130569,
    "id": 4835003,
    "name": "Hartford",
    "cod": 200
}

The current temperature is buried inside the object, in the “temp” property of the “main” subobject. I could just parse this using a JsonSlurper, but instead I decided to map the whole thing using Gson.

Gson wants a class structure that maps to the JSON hierarchy. Here’s mine, which I just stuffed into a single Groovy class called Model.groovy.

class Model {
    Long dt
    Long id
    String name
    Integer cod

    Coordinates coord
    Main main
    System sys
    Wind wind
    Clouds clouds
    Weather[] weather
}

class Main {
    BigDecimal temp
    BigDecimal humidity
    BigDecimal pressure
    BigDecimal temp_min
    BigDecimal temp_max
}

class Coordinates {
    BigDecimal lat
    BigDecimal lon

    String toString() { "($lat, $lon)" }
}

class Weather {
    Integer id
    String main
    String description
    String icon
}

class System {
    String message
    String country
    Long sunrise
    Long sunset
}

class Wind {
    BigDecimal speed
    BigDecimal deg
}

class Clouds {
    BigDecimal all
}

I added the data types based on reading the docs, which are pretty thin, and following the nested structure of the JSON data. The names of the classes aren’t important. It’s the property names that have to match the keys in the JSON maps for deserialization to work.

Using Gson is almost trivial. All I need is the fromJson method in the Gson class:

import groovy.transform.*
import com.google.gson.Gson

@ToString(includeNames=true)
class Model {
  ...
}

String url = 'http://api.openweathermap.org/data/2.5/weather?q=marlborough,ct'
String jsonTxt = url.toURL().text
Gson gson = new Gson()
println gson.fromJson(jsonTxt, Model)

Everything is converted to the Groovy class structure, just as expected.

Before printing the results, though, I need to do some data manipulation. If you looked at the current temperature value, you might have noticed it’s in Kelvin, of all things. As a US-based developer, I need to convert that to Fahrenheit.

def convertTemp(temp) {
    9 * (temp - 273.15) / 5 + 32
}

The time fields are based on “Unix time”, which measures seconds in the current epoch (beginning January 1, 1970 GMT). Java’s Date class has a constructor that takes a long representing milliseconds from the beginning of the epoch.

def convertTime(t) {
    new Date(t*1000)  // Unix time in sec, Java time in ms
}

Finally, the wind speed is in “meters per second” and I want “miles per hour”. When I was in high school (and dinosaurs roamed the Earth), I learned about the Factor Label method, which meant I could memorize a single length conversion and calculate any other.

def convertSpeed(mps) {
    // 1 m/sec * 60 sec/min * 60 min/hr * 100 cm/m * 1 in/2.54 cm * 1 ft/12 in * 1 mi/5280 ft
    mps * 60 * 60 * 100 / 2.54 / 12 / 5280
}

I added all of these to the Model class, and some getters to use them.

class Model {
  // ... as before ...

  def getTime() { convertTime dt }
  def getTemperature() { convertTemp main.temp }
  def getLow() { Math.floor(convertTemp(main.temp_min)) }
  def getHigh() { Math.ceil(convertTemp(main.temp_max)) }
  def getSunrise() { convertTime sys.sunrise }
  def getSunset() { convertTime sys.sunset }
  def getSpeed() { convertSpeed wind.speed }
}

Finally, here’s my nice, formatted toString method to print the results (also a part the Model class):

String toString() {
    """
    Name         : $name
    Time         : $time
    Location     : $coord
    Weather      : ${weather[0].main} (${weather[0].description})
    Icon         : http://openweathermap.org/img/w/${weather[0].icon}.png
    Current Temp : $temperature F (high: $high F, low: $low F)
    Humidity     : ${main.humidity}%
    Sunrise      : $sunrise
    Sunset       : $sunset
    Wind         : $speed mph at ${wind.deg} deg
    Cloudiness   : ${clouds.all}%
    """
}

I should mention that the Weather attribute of the Model class is a collection. Presumably that’s for when there are multiple weather stations associated with a given location. In the source code repository (linked below), I used Groovy’s each method to iterate over them all. Here I’m just using the first one.

The driver for the system is:

import com.google.gson.Gson

class OpenWeather {
    String base = 'http://api.openweathermap.org/data/2.5/weather?q='
    Gson gson = new Gson()

    String getWeather(city='Marlborough', state='CT') {
        String jsonTxt = "$base$city,$state".toURL().text
        gson.fromJson(jsonTxt, Model).toString()
    }
}

I like Groovy’s default arguments for methods. If I invoke the getWeather method without arguments (or as the weather property in the usual idiom), then the result is for “Marlborough, CT”. Otherwise I supply a city and a state and they’re used instead.

Clearly this needs to be tested, or at least the converters so. Here’s a Spock test for them:

import spock.lang.Specification

class ModelSpec extends Specification {
    Model model = new Model()

    def 'convertTemp converts from Kelvin to F'() {
        expect:
        32 == model.convertTemp(273.15)
        212 == model.convertTemp(373.15)
    }

    def 'convertSpeed converts from meters/sec to miles/hour'() {
        expect:
        (2.23694 - model.convertSpeed(1)).abs() < 0.00001
    }

    def 'convertTime converts from Unix time to java.util.Date'() {
        given:
        Calendar cal = Calendar.instance
        cal.set(1992, Calendar.MAY, 5)
        Date d = cal.time
        long time = d.time / 1000  // Java time in ms, Unix time in sec

        when:
        Date date = model.convertTime(time)

        then:
        d - date < 1
    }
}

The mps to mph value I got from putting “1 meter per second in mph” into Google, which gave me the proper result.

Just to make sure the calls are working properly, here are a couple of tests for the Model class.

import spock.lang.Specification

class OpenWeatherSpec extends Specification {
    OpenWeather ow = new OpenWeather()

    def 'default city and state return weather string'() {
        when:
        String result = ow.weather
        println result

        then:
        result  // not null is true in Groovy
        result.contains('41.63')
        result.contains('-72.46')
    }

    def "The weather is always great in Honolulu"() {
        when:
        String result = ow.getWeather('Honolulu', 'HI')
        println result

        then:
        result
        result.contains('21.3')
        result.contains('-157.86')
    }
}

Here’s a script to run the whole system:

OpenWeather ow = new OpenWeather()
println ow.weather  // called Marlborough, CT, but really Hartford

// Home of Paul King, co-author of _Groovy in Action_ and my personal hero
println ow.getWeather('Brisbane','Australia')

// Home of Guillaume Laforge, head of the Groovy project
// (also one of my heroes, along with Dierk Koenig, Graeme Rocher, Tom Brady, David Ortiz, ...)
println ow.getWeather('Paris','France')

// Have to check the weather in Java, right?
println ow.getWeather('Java','Indonesia')

// Any weather stations in Antarctica?
println ow.getWeather('', 'Antarctica')

Here are the results at the moment (Jan 7, 2014, at about 5:45pm EST):

Name         : Marlborough
Time         : Tue Jan 07 17:43:10 EST 2014
Location     : (41.63, -72.46)
Weather      : Clear (Sky is Clear)
Icon         : http://openweathermap.org/img/w/01n.png
Current Temp : 8.312 F (high: 11.0 F, low: 5.0 F)
Humidity     : 35%
Sunrise      : Tue Jan 07 07:16:25 EST 2014
Sunset       : Tue Jan 07 16:36:23 EST 2014
Wind         : 3.4448818898 mph at 258 deg
Cloudiness   : 0%

Name         : Brisbane
Time         : Tue Jan 07 17:36:03 EST 2014
Location     : (-27.47, 153.02)
Weather      : Clouds (broken clouds)
Icon         : http://openweathermap.org/img/w/04n.png
Current Temp : 78.566 F (high: 82.0 F, low: 77.0 F)
Humidity     : 46%
Sunrise      : Mon Jan 06 14:00:36 EST 2014
Sunset       : Tue Jan 07 03:47:48 EST 2014
Wind         : 10.51360057266 mph at 121 deg
Cloudiness   : 80%

Name         : Paris
Time         : Tue Jan 07 17:39:23 EST 2014
Location     : (48.85, 2.35)
Weather      : Clear (Sky is Clear)
Icon         : http://openweathermap.org/img/w/01n.png
Current Temp : 52.682 F (high: 54.0 F, low: 51.0 F)
Humidity     : 81%
Sunrise      : Tue Jan 07 02:42:36 EST 2014
Sunset       : Tue Jan 07 11:11:33 EST 2014
Wind         : 8.052970651396 mph at 220 deg
Cloudiness   : 0%

Name         : Batununggal
Time         : Tue Jan 07 17:43:35 EST 2014
Location     : (-6.96, 107.65)
Weather      : Clouds (scattered clouds)
Icon         : http://openweathermap.org/img/w/03n.png
Current Temp : 68.5904 F (high: 69.0 F, low: 68.0 F)
Humidity     : 91%
Sunrise      : Mon Jan 06 17:40:32 EST 2014
Sunset       : Tue Jan 07 06:10:58 EST 2014
Wind         : 2.9303865426 mph at 200 deg
Cloudiness   : 44%

Name         :
Time         : Tue Jan 07 17:43:35 EST 2014
Location     : (-78.33, 20.61)
Weather      : Clear (Sky is Clear)
Icon         : http://openweathermap.org/img/w/01d.png
Current Temp : -20.2936 F (high: -20.0 F, low: -21.0 F)
Humidity     : 38%
Sunrise      : Tue Jan 07 16:43:35 EST 2014
Sunset       : Wed Jan 08 04:43:35 EST 2014
Wind         : 15.770400858984 mph at 15.0058 deg
Cloudiness   : 0%

So, yeah, it’s cold outside. I also freely admit it’s a little weird seeing those non-US temperatures converted to Fahrenheit.

The Gradle build file for this system is almost trivial:

apply plugin:'groovy'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.codehaus.groovy:groovy-all:2.2.1'
    compile 'com.google.code.gson:gson:2.2.4'
    testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
}

All of this code can be found in the book’s Github repo. I added this to Chapter 2: Groovy by Example. Chapter 2 now uses a multi-project Gradle build, so it doesn’t look exactly that the one shown here, but it works the same way.

This process is easy enough to replicate for any RESTful service that returns JSON data. The time consuming part, if any, is writing the POGO structure to capture the JSON tree, but at least with POGOs the result is pretty simple.

————-

Now for a few personal notes. First, the latest Amazon review is simply beautiful. It’s from a person who knew Java and didn’t expect much from Groovy, but found out how much it could help. That’s exactly what I’m trying to achieve. In fact, I’ve been amazed that every single Amazon reviewer, no matter how much he or she did or didn’t like specific aspects of the book, definitely understood the goal and appreciated it.

Second, I have a few new projects in the works, but I think I can mention one of them. I’m currently generating a series of screencasts for the book, which I’m calling “Making Java Groovy: The Director’s Cut”. The goal is to discuss why the book was written as it was — what was included, what was left out, what has changed since its publication in the Fall of 2013, and anything else I think might be interesting or helpful. Everyone at Manning has been very supportive of the effort so far. I hope to make at least the first set of screencasts available soon.

Third, I’ll be giving a talk at the Boston Grails Users’ Group on Thursday, January 9, at 7pm. I hope to bring some books to add to the raffle, along with an admission to the NFJS event in the Boston area in March. If you’re in the area, please drop by and say hello.

Fourth, if you’ve read this far, you deserve a reward! If you use the coupon code “kousen37” at Manning.com, you can get 37% off any book at Manning. 🙂

Oh, and in case you didn’t hear me celebrating, I’m now officially a 2013 JavaOne Rock Star. Thank you very much to everyone who recommended me.

Finally, those of you battling the so-called “polar vortex”, stay warm! It’s cold here, but at least we’re still above 0 (F, not C). Next week I’m planning to brave the winds of Caradhras, otherwise known as central Minnesota. Brr.

As the saying goes, follow the great advice found on the side of a mayonnaise jar: “Keep cool but don’t freeze”.

%d bloggers like this: