chiastic-security chiastic-security - 6 months ago 14
Java Question

How to debug something that works under Java 7 but not under Java 8

I have a project with a unit test that works under Java 7, but not under Java 8. Is there a good way to investigate such things? (I'm sure that the test is correct; this suggests that there's a subtle bug in the implementation.)

Really I suppose what I would like is a quick way to identify where the code paths diverge. But this is hard, because there might be all sorts of differences in the code paths at a very low level through the JDK, and I don't want to get bogged down in irrelevant differences that are down to tiny optimisations.

So the nice thing would maybe be to ask at what top level trace the paths diverge; and then, starting from just before that point, to ask at what second level trace the paths diverge; and so on.

But I've no idea whether there's a way to do this. I fear I could waste a lot of time if I don't have a systematic approach.

The code, by the way, is the Apache Phoenix repository, where under Java 8, I get the following failure:

Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.006 sec <<< FAILURE! - in org.apache.phoenix.schema.PMetaDataImplTest
testEviction(org.apache.phoenix.schema.PMetaDataImplTest) Time elapsed: 0.006 sec <<< FAILURE!
java.lang.AssertionError: expected:<3> but was:<2>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
at org.junit.Assert.assertEquals(Assert.java:645)
at org.junit.Assert.assertEquals(Assert.java:631)
at org.apache.phoenix.schema.PMetaDataImplTest.testEviction(PMetaDataImplTest.java:98)

Answer

One of the most visible changes between Java 7 and Java 8 is a change to how hash data structures handle collisions - previously linked lists were used, now the entries are stored in balanced trees. In some cases this can affect the iteration order of these data structures. This isn't a language regression since these data structures explicitly do not specify an iteration order, but careless code and tests can easily be written that assume a certain order.

In rare situations, this change could introduce a change to the iteration order of HashMap and HashSet. A particular iteration order is not specified for HashMap objects - any code that depends on iteration order should be fixed.

~ Collections Framework Enhancements in Java SE 8

So the first thing I would do when debugging Java 7 -> 8 failures is look for miss-uses of HashMap, HashSet, and ConcurrentHashMap. Once you've found the mistake you have a few choices:

  • Replace the data structure with one that explicitly provides an iteration order, e.g. LinkedHashMap, TreeMap, or ImmutableMap.
  • Remove iteration-order assumptions from your production code (for instance assuming that a certain key/value will be the first element in a map).
  • Improve your tests to actually check for specified behavior (which elements) rather than unspecified behavior (what order). Often test data will be defined in an easy-to-write but incorrect way, like using the inline array syntax int[] expected = {1,2,3} and then looping over the elements. You can make this easier using Guava's static constructors (ImmutableSet.of(1,2,3)) or Truth's fluent assertion syntax (assertThat(someSet).containsExactly(1, 2, 3)).

If hash ordering proves to not be the root cause your issue is likely much more subtle and your best option will likely be to step through with a debugger, as suggested. Be sure to at least skim What's New in JDK 8 as well for clues.

Comments