The 30 second answer
Java 17 is a long-term support release, so coming from 8 or 11 you collect a lot. ZGC and Shenandoah became production-ready low-pause collectors in Java 15. NullPointerExceptions now say exactly which variable was null. JDK internals are strongly encapsulated, which is the usual upgrade blocker for old reflection-heavy libraries. You also get records, sealed classes, switch expressions and text blocks.
Highlights between Java 11 and 17:
- ZGC and Shenandoah became production features in Java 15 (JEPs 377 and 379).
- Helpful NullPointerExceptions (JEP 358, Java 14, on by default from Java 15), e.g.
Cannot invoke "String.length()" because "<local1>.name" is null. - Strong encapsulation of JDK internals (JEP 403, Java 17): the
--illegal-accessescape hatch no longer works; libraries need proper APIs or explicit--add-opens. - Enhanced pseudo-random number generators (JEP 356, Java 17): the
RandomGeneratorinterface and new algorithms. - Elastic Metaspace (JEP 387, Java 16) returns unused class-metadata memory to the OS more promptly.
- Packaging tool
jpackage(JEP 392, Java 16). - Removals and deprecations: Nashorn removed (JEP 372, Java 15); RMI activation removed (JEP 407) and the Security Manager deprecated for removal (JEP 411) in Java 17.
[Opinion] For most teams the upgrade cost is library compatibility, not code changes.
Sources: JEP 377: ZGC production, JEP 379: Shenandoah production, JEP 358: Helpful NullPointerExceptions, JEP 403: Strongly Encapsulate JDK Internals, JEP 356: Enhanced PRNGs, JEP 387: Elastic Metaspace, JEP 411: Deprecate the Security Manager