In one of our current projects, we are migrating from JDK5 Update 7 to JDK6 Update 12. One of our applications uses JCaptcha to secure input forms. We gain the biggest possible ease by configuring the captcha through Spring.
After switching one of our development machines to JDK6, we suddenly faced an IllegalArgumentException during initialization of the captcha servlet. Switching JBoss back to JDK5 solved the problem. This was leading nowhere though, because the target systems are all going to be switched to Java 6.
This is an excerpt from the stack trace:
java.lang.IllegalArgumentException: Color parameter outside of expected range: Red Green Blue at java.awt.Color.testColorValueRange(Color.java:298) at java.awt.Color.(Color.java:382) at java.awt.Color.(Color.java:357) at java.awt.Color.(Color.java:448)
A more in-depth investigation into the problem using the Eclipse Debugger yielded the following result:
Obviously, the Color constructor for three float arguments was being used. An excerpt from the spring beans configuration:
<bean id="captchaBackgroundColor" class="java.awt.Color"> <constructor-arg index="0"><value>255</value></constructor-arg> <constructor-arg index="1"><value>255</value></constructor-arg> <constructor-arg index="2"><value>255</value></constructor-arg> </bean>
The float constructor of java.awt.Color has this as its first line of code:
this( (int) (r*255+0.5), (int) (g*255+0.5), (int) (b*255+0.5));
This passes the arguments to the int constructor, which in our case is called as Color(65025, 65025, 65025). The immediate result of this is the aforementioned IllegalArgumentException.
The exact reason for our problem arises from a combination of multiple facts. If you aren’t much interested in technical details, skip the following list:
- In the project, Spring 2.0.0 is used. The constructor to be called is resolved via ConstructorResolver.autowireConstructor(…). On line 100 in that method, an array of potential constructors is created via reflection. Next, this array is sorted. The underlying Arrays.sort(…) gives a different result with JDK6 than with JDK5 (this seems to happen only on Windows machines)
- In the sorted array, the constructor Color(float, float, float) resides at a lower index than Color(int, int, int). With JDK5, the opposite is true.
- What follows is a loop that picks one constructor that is to be used for instantiation from the array. This loop determines the constructor to be used based on a comparison of “number of arguments” (trivial) and a so called “Type Difference Weight” (not so trivial).
- Type Diffence Weight means that a difference in class hierarchy is computed for parameter types and the related arguments. We want to use int arguments in order to instantiate our Color object. The type weight difference calculation method “climbs” the class hierarchy of each parameter type as long as no higher superclass can be found. As long as the superclass found is a type to which the related argument can be assigned, the type difference weight value is increased and the search continues. (AutowireUtils.getTypeDifferenceWeight(…))
- This obviously means that the type difference weight of float and int is 0, as both are primitives.
- If the found tdw is smaller than the smallest tdw found so far, the constructor being examined in the current loop run is set as the constructor to be used.
- Because the float constructor (using JDK6) is closer to the start of the array and because the tdw calculated in further loop runs cannot become smaller than the lowest tdw found so far (0 cannot be smaller than 0), the float constructor will be used in the end.
- The float constructor passes its arguments to the int constructor, and it does so by multiplying them by 255 and increasing them by 0.5
- The int constructor is called as Color(65025, 65025, 65025). This results in the IllegalArgumentException, because RGB values can only be between 0 and 255.
Back to the phenomenon: on the test system that has already been switched to Java 6, the captcha still runs without errors. Hence, the root of the problem is obviously that the constructor array has a different order after sorting on a Windows system than it has on a Linux system. The type difference weight mechanism should hopefully have become a little smarter too with newer Spring versions, given that autowiring still relies on that after all.
Nevertheless, we can solve the problem by adding explicit type settings to our Spring beans:
<bean id="captchaBackgroundColor" class="java.awt.Color"> <constructor-arg index="0" type="int"><value>255</value></constructor-arg> <constructor-arg index="1" type="int"><value>255</value></constructor-arg> <constructor-arg index="2" type="int"><value>255</value></constructor-arg> </bean>
This ensures that the desired constructor, which is java.awt.Color#Color(int r, int g, int b), is used.
The source code analysis I ran in as much depth as possible on Spring 2.0.0 and JDK6 Update 12 did not yield a precise finding as to why Arrays.sort(…) and the provided comparator from the Spring framework gives a different result on Windows systems than it does on Linux systems. Anyone able to shed light on this issue is invited to do so. 🙂
Conclusion: the devil is in the details. Even an assumed “trivial” change like an update of the Java version can lead to errors which are hard to find. Intensive and precise testing is vital in such cases!
Thanks a lot to Mike Wiesner, Senior Consultant at SpringSource, who helped me “in realtime” with a question regarding Spring 2.0.0!