Catching a dynamic typing failure in Groovy

Earlier today, a colleague asked me to help troubleshoot a very weird error in a Grails page he was working on.

On this page, he has a select element where the user is asked to choose a US state. The states list comes from a domain class that is mapped to a database table that holds state names and their ISO-8601 codes:

CODE_VALUE CODE_NAME
2 AK Alaska
9 DE Delaware
10 CA California
16 ID Idaho
50 TN Tennessee

<g:select name="state" from="${State.list()}" optionKey="codeValue" optionValue="codeName" value="${params.state}"/>

In the controller action for this page, he posts [params: params] back on the request in the return statement. So, after hitting submit on the page, he would expect to see that the select element retains the same state that was chosen by the user. (value=”${params.state}”)

The weird behavior he described was that the page seemed to work as designed for most values that he selected from the drop-down. However, for the first states in the list (AK through DE), upon submitting the form, the selected state would change. For example, if he selected “AL Alabama” (with a codeValue == 2), after submit, the form would have “TN Tennessee” selected. (codeValue == 50).

After puzzling through this for a while, we finally came to understand what was happening.

Groovy is dynamically typed. If you don’t explicitly specify a data type, then Groovy treats all values as a “def” – an instance of java.lang.Object. When Groovy has to perform an operation on a “def”, it makes an educated guess about what type to cast the value to, and it is right so often that we tend to forget that typing is happening at all.

But consider: Values posted back to the server from a GSP form in an http request are all returned as text. Grails conveniently parses form values out of the request and injects them into the controller action as a List named “params”, but it doesn’t know how to type them if they’re not actually Strings, so if you don’t cast them yourself, you get whatever type Groovy thinks they should be. My colleague was taking the params.state value right off of the request and then rendering it unmodified back into the view.

Now, if params.state was being typed as an Integer, everything should work as expected. But what if Groovy was blindly typing the value as a String? On a hunch, I asked my colleague to take a look at the
Unicode Basic Latin character set.

Case 1: He selects “ID Idaho”. Idaho’s codeValue, “16”, is submitted back to the controller and then reloaded into the select upon rendering. This works as expected.

Case 2: He selects “AL Alabama”. Alabama’s codeValue, “2”, is submitted back to the controller and then reloaded into the select upon rendering. The select now has “TN Tennessee”, codeValue “50”, selected.

The decimal Unicode value for the character “2” is 50.

What’s happening here is that if a two-character String representation of a numeric value (e.g., “16”) is given to Groovy, Groovy is correctly deducing that it needs to be cast as Integer. If a one-character String representation of a numeric value (e.g., “2”) is given to Groovy with no other instructions, Groovy is making the guess that this is a byte, and it is operating on the decimal value of the byte.

Once we understood what was happening, the fix was simple.

<g:select name="state" from="${State.list()}" optionKey="codeValue" optionValue="codeName" value="${params.state as Integer}"/>

I think that the lesson to be learned here is that while dynamically typed languages are great, a smart developer should never rely on them to always do the right thing. You don’t have to go so far as to force static typing in Groovy, but do use typed values as a habit, whenever you have the choice.

#dynamically-typed, #grails, #groovy