Fun with Time Zones in Java 8

[Note: Revised based on suggestions in the comments.]

They say that one way to identify a software developer is to whisper the word “timezone” in their ear and see if they shudder.

That’s certainly true for me, though my reaction is based more on travel and trying to arrange conference calls across time zones than actual coding. Like most scary things, I’ve tried to avoid the whole date/time API in Java, partly because prior to Java 8 the API is a tire fire and partly because the whole issue is like the “Here be dragons” section of a map.

herebedragons

Recently, however, I’ve been teaching Java 8 upgrade classes, and making Java 8 presentations at conferences on the No Fluff, Just Stuff tour. As part of those talks, I give an overview of the new java.time package.

The new package, by the creators of JodaTime, finally (finally!) provides an alternative to java.util.Date and java.util.Calendar. New classes like java.time.LocalDate, java.time.LocalTime, java.time.LocalDateTime and java.time.ZonedDateTime are now all available and much more powerful. If you used JodaTime in the past (no pun intended, but they’re hard to avoid), you’re already familiar with them, as the same people who wrote JodaTime in the first place wrote the new package.

I’m certainly not going to review the whole thing here, but I did want to mention a couple of fun examples.

First, I’ve known for some time that there are time zones in the world that are off by half-hour offsets rather than whole hours. To pick one, Indian Standard Time is UTC+05:30. When I mentioned that in class, I also said that someone once told me that there was a time zone in the world offset by 45 minutes. At the time I thought they were pulling my leg, but now I have the machinery to find out.

Once problem, however, is that abbreviations like EST or IST are no longer valid. The Wikipedia article on Time Zones discusses the issue, which claims that “such designations can be ambiguous”, where ECT could stand for Eastern Carribean Time, Ecuador Time, or even European Central Time. Instead, the ISO 8601 standard uses either offset designators, like UTC-05:00, or “region-based IDs”, like “America/New_York”.

(Speaking of the ISO 8601 standard, since there’s an XKCD cartoon on everything, here’s the one on that: https://xkcd.com/1179/ .)

Bringing it back to Java, the API defines a class called java.time.ZoneId, which has a static method called ZoneId.of(...) that takes a designator. You use that to create a ZonedDateTime. If you use an offset as the argument, then the time in the ZonedDateTime does not change, but if you use the region, the time will automatically adjust for Daylight Savings Time rules in that region.

[As you can imagine, the whole Daylight Savings Time issue is another rabbit hole I choose not to dive into. Those rules are discussed in a class called java.util.time.zone.ZoneRules, which refers to classes like ZoneOffsetTransition, ZoneOffsetTransitionRule, and ZoneRulesProvider. You can see how the complexity just goes up and up, especially because DST rules change frequently in different locations. Yikes.]

If you know the region ID, you can create a ZoneId using the of method. I have the opposite problem, however. I want to figure out the region ID given the offset.

Fortunately, the Java Tutorial has a section on ZoneId and ZoneOffset that actually addresses this problem. For some strange reason, however, their sample code doesn’t use the Java 8 streams and lambda expressions, so I decided to rewrite it. Here’s my version:

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.List;

import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.toList;

public class FunnyOffsets {
    public static void main(String[] args) {

        Instant instant = Instant.now();
        ZonedDateTime current = instant.atZone(ZoneId.systemDefault());
        System.out.printf("Current time is %s%n%n", current);

        System.out.printf("%10s %20s %13s%n", "Offset", "ZoneId", "Time");
        ZoneId.getAvailableZoneIds().stream()
            .map(ZoneId::of)
            .filter(zoneId -> {
                ZoneOffset offset = instant.atZone(zoneId).getOffset();
                return offset.getTotalSeconds() % (60 * 60) != 0;
            })
            .sorted(comparingInt(zoneId ->
                instant.atZone(zoneId).getOffset().getTotalSeconds()))
            .forEach(zoneId -> {
                ZonedDateTime zdt = current.withZoneSameInstant(zoneId);
                System.out.printf("%10s %25s %10s%n", zdt.getOffset(), zoneId,
                    zdt.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)));
            });
    }
}

That code requires some explanation. First, the ZoneId.getAvailableZoneIds() method returns a Set of Strings containing all the region IDs. After converting to a Stream, the map(ZoneId::of) expression transforms that into a stream of ZoneId instances.

Then I want to filter that stream to return only those ZoneIds that have an offset that isn’t evenly divisible by 3600 (= 60 sec/min * 60 min/hr). To get the offset, however, you need a ZonedDateTime, so I use the current Instant and use the atZone method with each ZoneId to get a ZonedDateTime, and then call its getOffset method. That, in turn, has a getTotalSeconds method, and I can do the modulus on that. At that point, I could have just printed them, but I decided to sort them by offset first.

The sorted method on Stream takes a java.util.Comparator. I could implement the Comparator as a lambda myself, but Java 8 also added several default and static methods to that interface. One of them is Comparator.comparingInt, which takes an ToIntFunction that transforms its argument into an int. Then sorted generates a Comparator that sorts the ints, which then sorts the collection based on the results.

Believe it or not, that whole map/filter/sorted paradigm gets much easier with practice. It was harder for me to write that explanation than to figure out the method calls.

To print the results, I wanted to show the offset in each time zone as well as its region name. The ZonedDateTime class has a method called withZoneSameInstant, which converts a given time to its equivalent in another time zone.

(That’s a very convenient method that I’ve needed my entire professional career, and justifies all the time (again, no pun intended) I’ve spent on this.)

Finally, printing them out was easier if I formatted the time, for which I used the DateTimeFormatter shown. The result right now is:

Current time is 2016-07-16T16:12:51.905-04:00[America/New_York]
    Offset               ZoneId          Time
    -09:30         Pacific/Marquesas   10:42 AM
    -04:30           America/Caracas    3:42 PM
    -02:30          America/St_Johns    5:42 PM
    -02:30       Canada/Newfoundland    5:42 PM
    +04:30                      Iran   12:42 AM
    +04:30               Asia/Tehran   12:42 AM
    +04:30                Asia/Kabul   12:42 AM
    +05:30              Asia/Kolkata    1:42 AM
    +05:30              Asia/Colombo    1:42 AM
    +05:30             Asia/Calcutta    1:42 AM
    +05:45            Asia/Kathmandu    1:57 AM
    +05:45             Asia/Katmandu    1:57 AM
    +06:30              Asia/Rangoon    2:42 AM
    +06:30              Indian/Cocos    2:42 AM
    +08:45           Australia/Eucla    4:57 AM
    +09:30           Australia/North    5:42 AM
    +09:30      Australia/Yancowinna    5:42 AM
    +09:30        Australia/Adelaide    5:42 AM
    +09:30     Australia/Broken_Hill    5:42 AM
    +09:30           Australia/South    5:42 AM
    +09:30          Australia/Darwin    5:42 AM
    +10:30       Australia/Lord_Howe    6:42 AM
    +10:30             Australia/LHI    6:42 AM
    +11:30           Pacific/Norfolk    7:42 AM
    +12:45                   NZ-CHAT    8:57 AM
    +12:45           Pacific/Chatham    8:57 AM

So not only are there regions with half-hour offsets, like “Canada/Newfoundland”, “Australia/Adelaide”, and “Pacific/Norfolk”, there are indeed time zones offset by 45 minutes, like “Asia/Katmandu”, “Australia/Eucla”, and “Pacific/Chatham”.

I haven’t been able to find the reasons for all the odd offsets, but they appear to be due to political compromises between two surrounding zones. Some are very recent adoptions, like the Mongolian one (“Asia/Kathmandu”), which wasn’t established until 1986.

On the guiding principle that anything I can do in Java I can do much more easily in Groovy, I decided to write a Groovy version. In this case, the Groovy JDK hasn’t done anything with the classes in java.time yet. Still, the normal Groovy simplifications lead me to this version:

import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle

LocalDateTime now = LocalDateTime.now();
List<ZonedDateTime> zdts =
    ZoneId.availableZoneIds
        .collect { now.atZone(ZoneId.of(it)) }
        .findAll { it.offset.totalSeconds % (60 * 60) != 0 }
        .sort { it.offset.totalSeconds }

ZonedDateTime current = now.atZone(ZoneId.systemDefault());
println "Current time is $current"
printf("%10s %20s %13s%n", "Offset", "ZoneId", "Time")
zdts.each {
    ZonedDateTime zdt = current.withZoneSameInstant(it.zone)
    System.out.printf("%10s %25s %10s%n", zdt.offset, it.zone,
        zdt.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)))
}

I could have used the same map/filter/sorted methods here that I used in Java, but I think this version is a bit more idiomatic. All the needed methods have been added directly to collections, so I don’t need to switch to streams first. That means I don’t need to switch back, either, so I need fewer steps. I also take advantage of the convention that property access (like offset or totalSeconds) is converted to the associated getter method (getOffset or getTotalSeconds) automatically. This time, just to show an alternative, I used the ZonedDateTime class instead of Instant and converted to a list before printing the values.

That was fun, but if you really want see how crazy time zones can get, check out this figure, from the Wikipedia article on time zones in Antarctica.

antarctica_time_zones

If that doesn’t make a developer shudder, nothing will.

I decided to print those out, too. Here’s my Java version:

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class AntarcticaTimeZones {
    public static void main(String[] args) {
        Instant now = Instant.now();
        ZoneId.getAvailableZoneIds().stream()
            .filter(id -> id.contains("Antarctica"))
            .map(id -> now.atZone(ZoneId.of(id)))
            .sorted(Comparator.comparingInt(zoneId -&amp;gt;
                    zoneId.getOffset().getTotalSeconds()))
            .collect(Collectors.toList());
            .forEach(zdt ->
                System.out.printf("%s: %s%n", zdt.getOffset(), zdt.getZone()));
    }
}

This time I filtered on region IDs with the word “Antarctica” and I didn’t bother with the static import for Comparator.comparingInt. The result this time is:

-04:00: Antarctica/Palmer
-03:00: Antarctica/Rothera
+03:00: Antarctica/Syowa
+05:00: Antarctica/Mawson
+06:00: Antarctica/Vostok
+07:00: Antarctica/Davis
+08:00: Antarctica/Casey
+10:00: Antarctica/DumontDUrville
+11:00: Antarctica/Macquarie
+12:00: Antarctica/McMurdo
+12:00: Antarctica/South_Pole

Yeah, good luck with that. The Groovy version is naturally shorter:

import java.time.ZoneId

ZoneId.availableZoneIds
    .findAll { it ==~ /.*Antarctica.*/ }
    .collect { now.atZone(ZoneId.of(it)) }
    .sort { it.offset.totalSeconds }

In case you’re wondering, orbiting spacecraft experience many sunrises and sunsets in a 24 hour period, so timezones are hopeless. The International Space Station (according to the Wikipedia article on time zones in space) just gives up and uses GMT. The same article says that the “common practice for lunar missions is to use the Earth-based time zone of the launch site or mission control”.

Timekeeping on Mars gets worse, because the length of the Martian day is approximately 24 hours and 39 minutes, which is why Matt Damon kept referring to a sol.

That reminds me of this quote from Men in Black:

Jay: Zed, don’t you guys ever get any sleep around here?
Zed: The twins keep us on Centaurian time, standard thirty-seven hour day. Give it a few months. You’ll get used to it… or you’ll have a psychotic episode.

I suspect that if I spend much more time (ugh, again — see how hard it is to avoid those puns?) on this, I may be vulnerable to the same problem, so I’ll take this as a good time (haha — that one was intentional) to end.

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”.

Making Java Groovy at JavaOne 2013

Monday morning I gave my “Making Java Groovy” presentation at JavaOne in San Francisco. This is my first trip to JavaOne, and the sheer size of it is rather overwhelming. Of course, it’s also obvious at almost every turn that JavaOne is the weak sister of Oracle Open World, but hey, it could have been IBM, right?

I’ve been thinking about this presentation for literally years. I give a related talk on the No Fluff, Just Stuff tour all the time, and I’ve done similar talks at DevNexus, Gr8conf.us, and more. But this is JavaOne — the heart of the Java universe — and doing well here is supposed to mean something. Besides, if the evals are good enough, you can become a JavaOne Rock Star, which is likely the closest I’ll ever get to being any kind of rock star, so there’s that.

The biggest thing I worried about, though, was condensing my talk into an hour. On the NFJS tour I’m accustomed to 90 minute blocks, and frankly I can talk about Groovy all day long (and frequently do in training classes). I kept cutting parts I liked, though, until I thought I had something reasonably condensed.

Perhaps it’s not surprising, therefore, that I was much more nervous for this talk than normal. Nate Schutta (fellow NFJS speaker and one of the best presenters I’ve ever met) likes to remind me that we speakers are an odd breed. It’s pretty strange to find somebody who both likes to code and likes to stand up in front of an audience and talk about it. Actually, it’s weird enough to do the latter. Most people find public speaking terrifying. Somehow we manage to like it and even look forward to it.

Since this was my only talk at the conference (probably a mistake on my part; I should have submitted more proposals), I knew I could use everything I had. At NFJS conferences, I worry that after two or three talks, everybody’s heard all my jokes, which is a serious disadvantage. Of course, that assumes they listened to them in the first place. 🙂

I still had time after setting up, so I did a couple of things to entertain the audience before the mics were turned on. One was that I went to http://speedtest.net and checked the speed of the ethernet connection, which came in at around 85 Mbps. Sweet. I did this while the projector was on, partly just to see the reaction and partly to make the “wow, I could have downloaded that whole Breaking Bad episode in five minutes” joke.

That is a joke, of course. I would never do anything our corporate masters at the MPAA (and the RIAA) wouldn’t want, and if I did, I certainly wouldn’t admit it in a public forum like this.

Then I opened up a tab in my browser to http://emergencykitten.com . I liked refreshing the page anyway, but I claimed I did it in case anything in the presentation went wrong, which might even have been true.

I noticed, btw, that there is a GitHub link on emergencykitten.com, which says that the site apparently runs using nodejs on Heroku. If you look in the data folder there, you’ll find kittens.yml (!), which contains the links to the Flikr pages for all the images.

I’ll come back to that in a moment.

JavaOne wanted me to use PowerPoint, which was a singularly uncomfortable proposition. I used it anyway, but felt free to make fun of it during the talk, especially because it became balky every time I switched from it to a demo and back. The slides are mostly for summaries anyway, since my talk is almost completely code based, but I did add some fun meme-based pictures.

For example, check out this one:

grinds_my_gears

The slides themselves are now on slideshare.net, so you can see the whole presentation here.

I start with a demonstration that you can execute compiled Groovy code with the Java command, if you just add the “groovy-all” jar file to your classpath. That’s a cool demo, but it also shows that you don’t need to modify your production Java environment at all in order to add Groovy to your system.

Then I talk about operator overloading, and how even though I don’t do it a lot, it shows up in all sorts of places in the Groovy JDK.

I jumped from there to POGOs, and wrote a POGO that had a couple of attributes and AST transformations for @ToString, @EqualsAndHashCode, and @TupleConstructor, before replacing all three with @Canonical.

I then did my favorite demo of accessing and using the Groovy v3 geocoder. The code for that is in my book’s GitHub repository in a couple of different places, along with Spock tests for both online and offline (!) usage.

This nice image followed:

success_kid_mjg

I went from there to my default JSON example, which is still based on ICNDB: the Internet Chuck Norris Database. When I use it now I generally use the firstName and lastName properties so I can replace “Chuck Norris” with Guillaume Laforge, or Paul King, or even Brian Goetz. As luck would have it, the site was up and running, so I was able to do that, too.

That leads to my favorite image:

most_interesting_mjg

I said I would come back to the Flikr issue, and since I want to add something technical to this post, I’ll do that here. Flikr has a REST API, but it’s rather more involved than I like to use in a presentation. Here’s my basic search:

String key = '...my key...'
String endPoint = 'http://api.flickr.com/services/rest?'
def params = [method: 'flickr.photos.search', api_key: key,
    format: 'json', tags: 'cat', nojsoncallback: 1,
    media: 'photos']

I’m searching for cat pictures, which as everyone knows is the reason the Internet was invented. Most of the parameters are required, but I used nojsoncallback=1 to keep from wrapping the JSON response in a method. The only tag I submit is ‘cat‘, but I might want to add some more because that doesn’t always give me what I want. You have to be careful with Flikr, though, because you can get back almost anything.

I assemble the needed URL and submit it in the usual way:

def qs = params.collect { it }.join('&')
String jsonTxt = "$endPoint$qs".toURL().text
def json = new JsonSlurper().parseText(jsonTxt)

Turn the map entries into list elements of the form “key=value” then join with an &; convert the string to a URL and download the text, then parse it with a JsonSlurper. This stuff becomes automatic after a while.

The complication with Flikr is that the response doesn’t actually include the image URL. Instead it breaks it into pieces, which I have to reassemble:

def urls = []
json.photos.photo.each { p ->
    urls << "http://farm${p.farm}.staticflickr.com/${p.server}/${p.id}_${p.secret}.jpg"
}

Drilling down into each photo element, I can extract the farm, server, id, and secret elements and use them to create the URL for the actual image. This I then insert into a Java ImageIcon:

new SwingBuilder().edt {
    frame(title:'Cat pictures', visible: true, pack: true,
        defaultCloseOperation: WC.EXIT_ON_CLOSE, 
        layout:new GridLayout(0, 2)) {
        urls[0..5].each { String url ->
            label(icon:new ImageIcon(url.toURL()))
        }
    }
}

The only odd part of this is the WC constant, which is an alias for javax.swing.WindowConstants. I build a GUI with six cat pictures in it, and I’m done.

Putting it all together (so you can copy and paste if you want — change the key to your own to make it run):

import groovy.json.*
import groovy.swing.SwingBuilder

import java.awt.BorderLayout as BL
import java.awt.GridLayout
import javax.swing.ImageIcon
import javax.swing.WindowConstants as WC

String key = '...insert your key here...'
String endPoint = 'http://api.flickr.com/services/rest?'
def params = [method: 'flickr.photos.search', api_key: key,
    format: 'json', tags: 'cat', nojsoncallback: 1,
    media: 'photos']
    
def qs = params.collect { it }.join('&')
String jsonTxt = "$endPoint$qs".toURL().text
def json = new JsonSlurper().parseText(jsonTxt)

def urls = []
json.photos.photo.each { p ->
    urls << "http://farm${p.farm}.staticflickr.com/${p.server}/${p.id}_${p.secret}.jpg"
}

new SwingBuilder().edt {
    frame(title:'Cat pictures', visible: true, pack: true,
        defaultCloseOperation: WC.EXIT_ON_CLOSE, 
        layout:new GridLayout(0, 2)) {
        urls[0..5].each { String url ->
            label(icon:new ImageIcon(url.toURL()))
        }
    }
}

I ran the script and got two cat pictures and four pictures of tractors. I was confused until somebody pointed out that they were probably Caterpillar tractors, which shows what kind of trouble you can get into on Flikr. Believe me, it could have been worse.

Bringing it back to the GitHub repo for emergencykitten.com, maybe in the future I’ll grab the Flikr URLs in their repo and do the same thing. Still, this is a reasonably cool demo, and why not live a bit dangerously anyway?

The talk went well and a good time was had by all. At least I hope so, though I won’t know for sure until I see the session surveys and find out of I’m a rock star after all.

For the record, I’d like to say that as Groovy Rock Stars go, if Paul King is Led Zeppelin, Guillaume Laforge is the Rolling Stones, and Graeme Rocher is the Beatles, then I want to be E.L.O. I always liked E.L.O. and owned several of their albums when I was a kid.

Actually, that’s not quite right. I didn’t own several of their albums. I owned several of their 8-track tapes. Whoa.

I have to say, though, that the most interesting experience of all was seeing my book on the shelves of the bookstore at the conference. I went in late in the day and was about to frown at the number of copies on the shelf when somebody walked up, took one, browsed through it, grinned wryly to himself, and actually bought the bloody thing. I was extremely tempted to introduce myself and offer to sign it or take a picture of him holding it, but in the end I decided that would be too creepy.

My wife, who came along to do sightseeing in San Francisco, agreed with that. Still, I’ll be at the conference until Thursday, so if anyone wants me to add my chicken scratch to their book, just ask around. I promise not to hover too close to the store.

Making Java Groovy: Operator Overloading

This is partly a spoiler for my “Making Java Groovy” talk that I normally do at NFJS events and plan to use in my similar talk at JavaOne, but to celebrate the book going to the printer I thought I’d try to contribute something technical here.

I’ve always felt that one of the keys to understanding Groovy was operator overloading. Operator overloading has a mixed reputation, conjuring up horror stories of developers changing + to be *, which might be good for job security but not good in general.

(Ultimately it’s not be good for job security, either.)

To be honest, I don’t do a lot of operator overloading in my own code, but I take advantage of it all the time. Most Groovy developers don’t realize how thoroughly operator overloading occurs throughout the Groovy JDK, among other places.

This web page lists all the operators in Groovy and their corresponding methods. What’s easy to overlook, however, is that every operator in Groovy actually invokes a method. That table doesn’t just provide you with options — it’s what’s actually happening under the hood. Note that it’s not just operators like + or * that call methods. Array-like access (the [] operator) calls getAt and putAt on maps, and even == invokes the equals method.

(Actually, == invokes the compareTo method if the class happens to implement java.util.Comparable, but according to the Comparable contract that ought to be equivalent.)

The code simplifications that result are dramatic. For example, consider the plus method in java.util.Date, which takes an int representing the number of days:

Date now = new Date()
Date then = now + 3
assert 4 == (now..then).size()

It’s also why you can not only concatenate with strings using +, you can also subtract substrings from them using the minus operator:

String s = 'silly string'
assert 'more silly string' == 'more ' + s
assert 'string' == s - 'silly '

Note that in each case, the operator is returning a new string rather than modifying the original. Strings are still immutable, even in Groovy.

Let me give a larger example that uses Groovy / Java integration to make coding easier. If you’ve ever done financial calculations in Java (or if you’ve ever seen Office Space), you know you need to use java.math.BigDecimal for the amounts rather than just doubles. But BigDecimal in Java is an annoying class to work with, because (1) you have to instantiate it, (2) you have to remember to use the constructor that takes a String, or the results are not predictable (see the JavaDocs for details), and (3) you lose all your operators and have to use all the associated method calls, keeping in mind that (4) all instances of the class are immutable. That makes for a rather verbose class when all you want is to wrap a numerical value.

You can do all that, or you can take advantage of the fact that the native data type for floating point values in Groovy is already BigDecimal. Not only that, but the Groovy JDK includes methods like plus, minus, and multiply in the java.lang.Number class (the superclass of BigDecimal), so you can use your normal operators. The result is a class like:

package com.mjg

import groovy.transform.Canonical

@Canonical
class Account {
    BigDecimal balance = 0.0
    
    BigDecimal deposit(BigDecimal amount) {
        balance += amount
    }
    
    BigDecimal withdraw(BigDecimal amount) {
        balance -= amount
    }
}

The only attribute is the balance, stored as a BigDecimal, initialized to zero. Since the attribute lacks a public or private modifier, Groovy automatically generates public getter and setter methods for it. The deposit and withdraw methods take advantage of operator overloading to update the balance using simple + and – operators. Finally, the Canonical AST transformation adds equals, hashCode, and toString methods to Account, as well as a “tuple” constructor that can be called from Java. That’s a lot of mileage for 16 lines of code.

Before I use it from Java, let me verify that it works so far:

package com.mjg

// Normal Groovy ctor that sets the property
Account acct = new Account(balance:250)
assert 250 == acct.balance
assert 300 == acct.deposit(50)
assert 200 == acct.withdraw(100)

// check the tuple ctor
acct = new Account(150)
assert 150 == acct.balance

// toString method
assert 'com.mjg.Account(150)' == acct.toString()

// equals and hashCode
Set accounts = [new Account(balance:250), new Account(250)]
assert accounts.size() == 1

Accounts can be created using the normal map-based constructor or the tuple constructor, which in this case has only a single argument (because the class has a single attribute). Accessing the balance property actually invokes the getBalance method, which is supplied by Groovy automatically. The generated toString method is as shown, and to prove that both equals and hashCode are working I added two identical accounts (instantiated by either constructor) into a Set and verified that Groovy added only one of them.

That’s all well and good, but if I can’t verify a Groovy class using Groovy tests, I might as well give it up. Instead, I want to access the Groovy class from Java. Here, therefore, is a highly contrived Bank class, in Java:

package com.mjg;

import java.math.BigDecimal;

public class Bank {
    public Account openAccount(double initialBalance) {
        Account acct = new Account();
        acct.deposit(new BigDecimal(initialBalance + ""));
        return acct;
    }

    public BigDecimal getBalance(Account acct) {
        return acct.getBalance();
    }
    
    public BigDecimal addFunds(Account acct, double amount) {
        return acct.deposit(new BigDecimal(amount + ""));
    }
    
    public BigDecimal removeFunds(Account acct, double amount) {
        return acct.withdraw(new BigDecimal(amount + ""));
    }
}

The Bank has a method to open an account using a double, which is wrapped in a BigDecimal using the String-based constructor. Then addFunds and removeFunds just delegate to the associated methods in Account.

To test this, I might as well switch back to Groovy again, but in a normal JUnit test:

package com.mjg

import static org.junit.Assert.*;

import org.junit.Test;

class BankTests {
    Bank bank = new Bank()
    Account acct = bank.openAccount(250);
    
    @Test
    void testOpenAccount() {
        assert acct
    }
    
    @Test
    void testGetBalance() {
        assert 250 == bank.getBalance(acct)
    }
    
    @Test
    void testAddFunds() {
        bank.addFunds(acct, 50)
        assert 300 == bank.getBalance(acct)
    }
    
    @Test
    void testRemoveFunds() {
        bank.removeFunds(acct, 100)
        assert 150 == bank.getBalance(acct)
    }
}

All the tests work without a problem, demonstrating how easy it is to switch from Groovy to Java and back again. If you ever have to add code to an existing Java system that involves BigDecimal, writing a class in Groovy is a very easy way to do it.

In my book, incidentally, the chapter on Spring and Groovy uses a similar Account class, but with an added id. Then I implement an AccountDAO interface (written in Java, of course) using the a Groovy class that takes advantage of Spring’s JdbcTemplate (again from Java), complete with Spock tests. If you want to see the source code now, just check out ch07 in the GitHub repository for the book.

As in this example, when I code in Groovy I don’t often do operator overloading, but the effects of it show up everywhere. Hopefully these simple examples will help you take advantage of it in the future.

BTW, I received notice from Manning that I will have actual hard copies of Making Java Groovy at the SpringOne2GX conference next week. Even if you don’t want one, please come by and say hello. Don’t be surprised if I can’t stop grinning the entire time.

NetBeans 6.1 is a lot better than I thought

This week I’m in New Haven, CT, teaching a class that combines UML and Java Web Services (an odd combination to be sure).  The client wanted to use NetBeans as their primary IDE, and I always try to accommodate that if I can.

My last exposure to NetBeans was back in version 5.5, I think, when a couple of friends suggested I give it a whirl.  I’ve been an Eclipse user since version 1, so I’m very comfortable with that environment.  As part of giving a presentation at my local Java User’s Group, I also got a license for IntelliJ’s IDEA, so I’ve been playing with that off an on.  When I don’t want to use a heavy IDE, I generally stick with UltraEdit, which does a decent job, even with Groovy code.

Nevertheless, I downloaded and installed NetBeans 6.1.  I have to say that I’ve been very pleasantly surprised by the editor.  The UML support is surprisingly good, including the capability to generate sequency diagrams from code.  But its JAX-WS and JAXB support has been outstanding.  I’ve been using them with the embedded Glassfish server and really enjoyed it.

I’m in a bit of a rush (have to get back to class) or I’d say more.  What I can say, though, is that if the NetBeans group really does deliver on Groovy and Grails support in NB 6.5, as promised, I’m definitely going to try it.  I’m still mostly an Eclipse person (and the commercial MyEclipse tool as well).  I know it’s practically heresey to say so these days, but I’m still finding that IntelliJ slows me down a lot more than it speeds me up.  But this single week with NetBeans has been a revelation.  I can only hope the Groovy and Grails support when it comes out in October is as good as the rest of the IDE.

Nothing makes you want Groovy more than XML

I’m in Delaware this week teaching a course in Java Web Services using RAD7. The materials include a chapter on basic XML parsing using Java. An exercise at the end of the chapter presented the students with a trivial XML file, similar to:


<library>
  <book isbn="1932394842">
    <title>Groovy in Action</title>
    <author>Dierk Koenig</author>
  </book>
  <book isbn="1590597583">
    <title>Definitive Guide to Grails</title>
    <author>Graeme Rocher</author>
  </book>
  <book isbn="0978739299">
    <title>Groovy Recipes</title>
    <author>Scott Davis</author>
  </book>
</library>

(with different books, of course) and asked the students to find a book with a particular isbn number and print it’s title and author values.

I sighed and went to work, producing a solution roughly like this:


import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class ParseLibrary {
    public static void main(String[] args) {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        Document doc = null;
        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            doc = builder.parse("books.xml");
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        NodeList books = doc.getElementsByTagName("book");
        for (int i = 0; i < books.getLength(); i++) {
            Element book = (Element) books.item(i);
            if (book.getAttribute("isbn").equals("1932394842")) {
                NodeList children = book.getChildNodes();
                for (int j = 0; j < children.getLength(); j++) {
                    Node child = children.item(j);
                    if (child.getNodeType() == Node.ELEMENT_NODE) {
                        if (child.getNodeName().equals("title")) {
                            System.out.println("Title: "
                                + child.getFirstChild().getNodeValue());
                        } else if (child.getNodeName().equals("author")) {
                            System.out.println("Author: "
                                + child.getFirstChild().getNodeValue());
                        }
                    }
                }
            }
        }
    }
}

The materials didn’t supply a DTD, so I didn’t have any ID attributes to make it easier to get to the book I wanted. That meant I was reduced to continually using getElementsByTagName(String). I certainly didn’t want to traverse the tree, what with all those whitespace nodes containing the carriage-return/line-feed characters. So I found the book nodes, cast them to Element (because only Elements have attributes), found the book I wanted, got all of its children, found the title and author child elements, then grabbed their text values, remembering to go to the element’s first child before doing so.

What an unsightly mess. The only way to simplify it significantly would be to use a 3rd partly library, which the students didn’t have, and it would still be pretty ugly.

One of the students said, “I kept waiting for you to say, ‘this is the hard way, now for the easy way,’ but you never did.”

I couldn’t resist replying, “well, if I had Groovy available, the whole program reduces to:


def library = new XmlSlurper().parse('books.xml')
def book = library.books.find { it.@isbn == '1932394842' }
println "Title: ${book.title}\nAuthor: ${book.author}"

“and I could probably shorted that if I thought about it. How’s that for easy?”

On the bright side, as a result I may have sold another Groovy course. 🙂 For all of Groovy’s advantages over raw Java (and I keep finding more all the time), nothing sells it to Java developers like dealing with XML.

Library quirk in the new Eclipse, and random framework musings

I’ve been working with Struts 2 recently, and like most frameworks it relies on a set of jar files.  I like to work with Eclipse as my primary IDE.  In that framework, my normal mode of operating is to create a User Library containing the jar files I need and then add it to my Dynamic Web Projects through the Java Build Path configuration.

It seems that the system has changed in Eclipse 3.3, aka Europa.  I’m working with the Web Tools Platform plugin, so at least I have dynamic web project capabilies.  I can also set up a Tomcat server easily.

What’s changed is that now, when I add a User Library to the build path, Eclipse warns me that it won’t be deployed to the server.  The warning is:

“Classpath entry org.eclipse.jdt.USER_LIBRARY/libname will not be exported or published.  Runtime ClassNotFoundExceptions may result.”

Yer darn tootin’ they’ll result.  I suppose this is a feature, not a bug, because I have had situations where the libraries were already added to my server’s classpath so I didn’t want to duplicate them in the war file.  Also, some war files become enormous because of all the libraries used.  (The last time I wrote a simple app that used JPA, Spring, and Hibernate the resulting war file was huge.  And the size of the war file that comes with a trivial Grails application is easily over 10 megs.)  Now Eclipse leaves the libraries out of the war by default.

I’m reminded of when the Collections framework for Java was created, and ArrayList turned out to be exactly like Vector but without the synchronization.  The theory seemed to be that you can always add synchronization later (Collections.synchronizedList(myList)), but that you couldn’t remove it from Vector when you didn’t need it.  This change has the same feel to it — you can always figure out a way to deploy the libraries, but you never had a way to get rid of them short of editing the resulting war file.

That’s not necessarily a good thing for me, though.  I’m always tempted to add libraries to my server’s classpath, partly to keep the size of the individual war files down, and partly because I tend to use the same libraries over and over and I hate packing them into every single application.  All those repeated files feel like a waste.

Still, it’s dangerous not to include the jar files in the web application itself.  Version problems can easily arise, especially because I tend to upgrade to the latest version of a given library whenever I can.  For that reason, I’ve resisted the temptation to, say, put Hibernate and Spring into my server’s lib directory.

So what do I do in Eclipse?  It turns out there are two solutions:

1. Declare the libraries to be module dependencies for the war.  That’s done by going into the project properties, then Properties –> J2EE Module Dependencies –> Select All.  Then Eclipse treats the libraries the same way it would any dependent project.

Eclipse Properties J2EE Module Dependencies

2. The other approach is to go on the file system and copy the jar files directly into the WEB-INF/lib directory of the project, then do a refresh.  Apparently Eclipse is smart enough to then add the jars to the classpath for my source code.

I’m not sure which way I’ll prefer in the future.  Eclipse does have a Navigator view which supports drag-and-drop copying, so that’s easy enough.  Still, I hate to leave the IDE when I’m in the middle of using it.

This is also one of the few situations where using MyEclipse wouldn’t make any difference.  Version 6 truly rocks, but it doesn’t have a predefined library for Struts 2.  Maybe Struts 2 was still too new when MyEclipse 6 was being developed.

As an aside, it should be interesting to see which way the framework wars will turn in 2008.  There’s still a huge installed base of Struts 1.* clients out there, but many of them are looking at other options.  The other page-based framework I’ve used is Spring Web MVC, which has its own learning curve.

JSF is part of the Java EE spec, but if you’re used to a page-based framework the switch to a component-based framework is not trivial.  Tapestry is still out there, and it has a few very fervent advocates, but I haven’t heard much growth in it.  I also haven’t gotten any calls about it in quite some time.  Seam is growing, too, but I expect that’ll be popular among the same crowd that likes JSF, since it’s based on JSF anyway.   Of course, there’s also Google Web Toolkit if you want to build a desktop application inside a browser.

Struts 2 is very interesting, and has the modern feel of interceptors and filters and POJOs (oh my!).  It has Spring-like dependency injection, though I think they use Guice by default rather than Spring, not that it really matters.  I never learned WebWork before the merger, so the learning curve has been pretty substantial for me.  Personally I feel that Struts 2 is so fundamentally different than Struts 1 that I’m surprised they kept the Struts name.  That marketing decision may make all the difference, however.

I’ll keep an eye on all of them, of course, but for my own applications, I like Grails. 😉

(Yes, I know that’s the same as saying Spring Web MVC.  We’ll see how that plays out, too.)

%d bloggers like this: