1) Certain libraries that were part of the JDK being moved out of the JDK, which usually required adding them as a module or dependency in $BUILD_TOOL_INSTRUCTIONS_FILE
2) Internal JVM APIs that weren't public being used via reflection by clever libraries, and then when they change, those libraries break
3) Bytecode emitting libraries - various frameworks love these, but bytecode that worked for Java 8 can fail on Java 9. Hence Spring 4 only supports JDKs 6 - 8. So to move to JDK 9+, you had to upgrade Spring to 5.x, and things that were tied to your version of Spring... and this process can very often suck.
4) New versions of library X only being available in class formats that Java 8 can't run. I encountered this with Jetty - version 10+ only support Java 11+, so if you're stuck on Java 8, you're limited to bug fix releases on the last version that supported Java 8.
Since Java 9, the JVM has been warning if you use internal APIs. As of Java 17, they have started enforcing strong encapsulation of those APIs ("private means private yo"), to give the JDK freedom to evolve without worrying about every library that got clever with the internals of the JVM. But given the very long lead time and ample messaging on this, I don't expect Java 17 breaking too many things.
At my last job, we had a rather large monolithic build that was tied to Java 8 because some modules would die a horrible death when compiled with or for a higher version Java. So I introduced Maven and Gradle toolchains[1] so that individual modules could compile using their preferred Java version, which freed up new modules/apps to use Java >8 as they saw fit. All the legacy apps that broke on Java 9+ could stay on 8, but the rest of the project was freed from their legacy crap.
1) Certain libraries that were part of the JDK being moved out of the JDK, which usually required adding them as a module or dependency in $BUILD_TOOL_INSTRUCTIONS_FILE
2) Internal JVM APIs that weren't public being used via reflection by clever libraries, and then when they change, those libraries break
3) Bytecode emitting libraries - various frameworks love these, but bytecode that worked for Java 8 can fail on Java 9. Hence Spring 4 only supports JDKs 6 - 8. So to move to JDK 9+, you had to upgrade Spring to 5.x, and things that were tied to your version of Spring... and this process can very often suck.
4) New versions of library X only being available in class formats that Java 8 can't run. I encountered this with Jetty - version 10+ only support Java 11+, so if you're stuck on Java 8, you're limited to bug fix releases on the last version that supported Java 8.
Since Java 9, the JVM has been warning if you use internal APIs. As of Java 17, they have started enforcing strong encapsulation of those APIs ("private means private yo"), to give the JDK freedom to evolve without worrying about every library that got clever with the internals of the JVM. But given the very long lead time and ample messaging on this, I don't expect Java 17 breaking too many things.
At my last job, we had a rather large monolithic build that was tied to Java 8 because some modules would die a horrible death when compiled with or for a higher version Java. So I introduced Maven and Gradle toolchains[1] so that individual modules could compile using their preferred Java version, which freed up new modules/apps to use Java >8 as they saw fit. All the legacy apps that broke on Java 9+ could stay on 8, but the rest of the project was freed from their legacy crap.
[1]: https://docs.gradle.org/current/userguide/toolchains.html