The "it worked fine in Java 8" dilemma

Java 9 fails and how to fix them

Gabriela Motroc
Java 9

© Shutterstock / stickerama

These days, Java 9 developers have their hands full with Jigsaw but that doesn’t mean the community is sitting around doing nothing until September. They took Java 9 for a spin and discovered that not all Java 8 code reacts well to Java 9.

Let’s start by saying that this is a judgement-free zone. As Nicolai Parlog, the author of CodeFX and a passionate software developer, said in the description of the http://java9.wtf/ page, the idea is not to play the blame game but to present the things that break on Java 9.

Trouble ahead: Java 9 hiccups

New compilation errors

Nicolai pointed out that the changes to javac may cause new compilation errors. He explained that the root of the compile errors might be the fact that the Java 9 compiler’s type inference works differently in some cases. “All files in this project compile fine with Java 8 but fail with Java 9,” he wrote. Nicolai urged readers to look for comments // fail to see what doesn’t work and // pass for fixes that make it work in Java 9.

Some errors can come from casts like (int) Comparable<T>:

public void spinnerModel(SpinnerNumberModel spinnerModel) {
	// this could have happened somewhere else:
	// spinnerModel.setMinimum(0);
	// spinnerModel.setMaximum(360);
	// spinnerModel.setValue(45);

	int newValue = (int) spinnerModel.getValue() + 1;
	newValue = Math.min(newValue, (int) spinnerModel.getMaximum()); // fail
	newValue = Math.max(newValue, (int) (Object) spinnerModel.getMinimum()); // pass
	spinnerModel.setValue(newValue);
}

Some shenanigans with generic arrays stop working:

public static <I> Optional[] lift(I[] array) { ... }
public static <T> T[] flatten(Optional<T>[] array) { ... }

private Stream<T[]> streamSelectionPaths() {
	Object[][] things2d = new Object[0][0];
	return Arrays
			.stream(things2d)
//			.map(thing -> (Optional<T>[]) lift(thing)) // pass
			.map(GenericArray::lift) // fail
			.map(GenericArray::flatten);
}
(Last checked: 8u131 and 9-ea+172-jigsaw)

Next stop: Maven JAXB2 Plugin

The Maven JAXB2 Plugin does not process schema bindings on Java 9, Nicolai wrote and explained that the problem can be demonstrated by running the plugin and afterwards testing the existence of the expected classes. However, that’s not the most annoying part: one cannot recreate the error without the plugin.

The code in wtf.java9.maven_jaxb2_plugin is a simplified (and accidentally “rightified”?) version of the code the plugin is running but it works on both Java 8 and Java 9 as indicated by another test.

Running the project with mvn clean test creates two generated-jaxb-*folders, one for the sources created by the plugin the other by the JAXB API. While they are identical on Java 8, the same cannot be said for Java 9. As Nicolai explained, the tests try to verify that the sources are present and accordingly fail on Java 9.

(Last checked: 8u131 and 9-ea+172-jigsaw)

The published version of this project uses version 0.13.2 of the JAXB2 plugin. Efforts to make it work with a Java 9 specific version were unsuccessful as well. Those efforts can be tracked in this private branch of the Maven JAXB2 Plugin.

Different variants were implemented in order to slowly approach the plugin’s implementation, especially regarding the used EntityResolver. You can see them in EntityResovlerFactory and select them the Maven property entity.resolver. For example:

mvn clean test -Dentity.resolver=simple

By default, the plugin’s approach is copied as closely as possible.

SEE ALSO: Java 9 features elucidated: JDK 9 Early Access documentation has been released

Third strike: Noto Sans fonts

You should know that working with Noto Sans fonts on Java 9 is not the same as working wth it on Java 8 — speed wise (at least on Linux). According to Nicolai, the problem seems to be the creation of the T2KFontScaler, which happens deep in the bowels of computing a component’s preferred size:

Font2D font = FontUtilities.getFont2D(new Font("Noto Sans CJK JP Black", 0, 12));
Constructor<?> constructor = Class
		.forName("sun.font.T2KFontScaler")
		.getConstructor(Font2D.class, int.class, boolean.class, int.class);
constructor.setAccessible(true);
// for this to not end in a heap dump you need to have Noto Sans CJK JP Black
// installed on your machine
Object scaler = constructor.newInstance(font, 0, true, 18604592);

It gets worse: if this happens in a tight loop (e.g. to display the size of a list of labels, each sporting a different font), the only thing left to do is to exclude these fonts.

The test tries to demonstrate the performance variance by failing the test if it runs longer than half a second. However, it’s up to the machine to “decide” whether that is enough to succeed on Java 8 and fail on Java 9.

(Last checked: 8u131 and 9-ea+172-jigsaw)

XML Transformer gone wild

The behavior of javax.xml.transform.Transformer has changed considerably.

Let’s take an example:

// prepare an XML file as a DOM Source
String xml = "...";
Document document = DocumentBuilderFactory
		.newInstance()
		.newDocumentBuilder()
		.parse(new InputSource(new StringReader(xml)));
// maybe add some nodes or CDATA content

// transform the XML
Source source = new DOMSource(document);
Transformer transformer = TransformerFactory.newInstance().newTransformer();
// maybe configure transformer
StringWriter outputWriter = new StringWriter();
transformer.transform(source, new StreamResult(outputWriter));
String transformedXml = outputWriter.toString();

// now compare `xml` and `transformedXml`

The behavior between Java 8 and 9, with and without configuration, and with and without xml:space="preserve" differs. This is the most common behavior:

  • existing indentation remains
  • new nodes, including CDATA are inline

This can be noticed if the transformer was not configured beyond defaults and regardless of xml:space="preserve". However, when you configure the transformer with OutputKeys.INDENT=yes and indent-amount=4 both Java 8 and 9 behave differently. On Java 8 you get the following:

  • existing indentation remains
  • new nodes are indented according to the settings
  • CDATA is not indented

As far as Java 9 is concerned this is what happens:

  • the entire document is indented according to settings, including a bunch of spurious empty lines
  • accordingly, new nodes are indented
  • CDATA is put onto new lines and indented as well, fundamentally changing the XML!

Nicolai added that, with further configuration, it becomes apparent that xml:space="preserve" behaves differently as well. Although it has no effect whatsoever on Java 8, the same does not apply on Java 9 (it drops us back into the “nothing changes” case described first).

The test demonstrates that behavior, using JUnit 5’s nested tests.

(Last checked: 8u131 and 9-ea+172-jigsaw)

Contribute

Contributions are very welcome and if you have any questions, just open an issue. However, if you want to contribute, make sure your demo checks as many of these boxes as possible:

  • it should be as small as possible
  • it should be a Maven module, where mvn clean test passes on Java 8 but fails on Java 9
  • it should demonstrate the behavior without much editing:
    • if compiler or Surefire need different arguments on different releases, use profiles
    • if the entire Maven run needs arguments, have a look at how .mvn/jvm.config works
  • its README should describe the problem, possibly show some code (and link to more), and also point out the solution if one is available; also, don’t forget to link to it from above
  • the README should also mention the Java 8 and 9 versions on which the described behavior was observed

Check out the page and/or the Github repository

asap

asap

Author
Gabriela Motroc
Gabriela Motroc is an online editor for JAXenter.com. Before working at S&S Media she studied International Communication Management at The Hague University of Applied Sciences.

Comments
comments powered by Disqus