Java 17 Features
Java 17 (September 2021) is the second major LTS (Long-Term Support) release after Java 11 — many companies migrate straight to this version because it bundles several years of improvements at once (every feature from Java 12 through 17).
The biggest highlights: Records (data classes in one line), Sealed Classes (controlled inheritance), Pattern Matching for instanceof (auto-casting), Text Blocks (multi-line strings made easy), and Switch Expressions.
Java 17 features come up a lot in interviews, especially Records and Sealed Classes — because they change the way modern Java code gets written, cutting down on boilerplate.
// Record — ek line mein data class
record User(String name, int age) {}
User u = new User("Aarav", 25);
System.out.println(u); // User[name=Aarav, age=25]
// Pattern Matching for instanceof
Object obj = "Hello";
if (obj instanceof String s) {
System.out.println(s.length()); // seedha use, cast nahi chahiye
}- Java 17 = LTS release (Sept 2021)
- Records = data classes in one line
- Sealed Classes = controlled inheritance
- Pattern Matching for instanceof = auto-cast
- Text Blocks = multi-line strings made easy
Creating a simple data-holder class (like a Point with just x and y) in a normal class means writing a lot of boilerplate — a constructor, getters, equals(), hashCode(), toString(), all by hand (or generated by an IDE). The record keyword generates all of this automatically in one line.
Writing record Point(int x, int y) {} makes the compiler generate: a constructor (that sets both fields), getter methods (x() and y() — no "get" prefix), and properly implemented equals(), hashCode(), toString(). Records are implicitly final (can't be extended), and their fields are implicitly final too (immutable).
Records are best when you just need to carry data, with no complex behavior — like API responses, DTOs (Data Transfer Objects), or coordinate pairs. If a class needs methods/logic too (not just data), a normal class is still the better fit.
record Point(int x, int y) {}
Point p1 = new Point(3, 4);
System.out.println(p1.x()); // 3 (getter, "get" prefix nahi)
System.out.println(p1); // Point[x=3, y=4] (auto toString)
Point p2 = new Point(3, 4);
System.out.println(p1.equals(p2)); // true (auto equals, values compare hote hain)Anyone can normally extend/implement a class/interface — the sealed keyword lets you control *exactly* who's allowed to extend/implement it. This is especially useful when you know upfront exactly what all the possible subtypes are (like a Shape that can only be Circle, Square, or Triangle, and nothing else).
Writing sealed class Shape permits Circle, Square, Triangle {} means only these three classes can extend Shape — no outside code can create a new subtype. Every permitted subclass has to explicitly declare itself: final (can't be extended further), sealed (controls its own further extension), or non-sealed (back to normal, anyone can extend it).
The benefit: in switch statements, the compiler knows exactly what all the possible types are, so "exhaustiveness checking" becomes possible (we'll see this with pattern matching in the next subtopic) — the compiler can confirm every case is covered even without a default case.
sealed interface Shape permits Circle, Square, Triangle {}
final class Circle implements Shape { double radius; }
final class Square implements Shape { double side; }
final class Triangle implements Shape { double base, height; }
// Koi doosri class Shape implement NAHI kar sakti — compiler error degaIn older Java, after an instanceof check you had to manually cast — two steps, a bit repetitive: if (obj instanceof String) { String s = (String) obj; ... }. In Java 16+ (stable in 17), instanceof can now "bind" a variable itself if the check passes.
Writing if (obj instanceof String s) means that if obj is a String, s automatically points to that already-cast value — you can use s directly inside that if block, no manual cast needed. This makes code shorter and safer (less risk of a ClassCastException, since the compiler itself does the type-check).
Object obj = "Hello Java 17";
// Purana tareeka:
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// Java 16+ pattern matching:
if (obj instanceof String s) {
System.out.println(s.length()); // s seedha use ho sakta hai, cast nahi chahiye
}Writing a multi-line String (like JSON, HTML, a SQL query) in older Java was messy — a \n at the end of every line, escaping quotes, concatenating with +. Text blocks (starting and ending with """) get rid of all of that.
A text block automatically strips leading whitespace "smartly" (removes the common indentation across all lines), converts line breaks to \n, and you don't need to escape quotes (except in edge cases).
// Purana tareeka:
String json1 = "{\n" +
" \"name\": \"Aarav\",\n" +
" \"age\": 25\n" +
"}";
// Text block (Java 15+):
String json2 = """
{
"name": "Aarav",
"age": 25
}
""";Switch Expressions (Java 14+, stable) are shorter and safer than the old switch statement — they use arrow syntax (case X -> result), there's no fall-through (no need for a break at the end of every case), and they can "return" a value too (usable directly in an assignment).
In Java 17, Pattern Matching for switch arrived as a preview feature (stabilized in later versions) — now you can branch in a switch based on type too, with pattern binding just like instanceof: case Integer i -> ..., case String s -> ....
// Purana switch statement:
int day = 3;
String name;
switch (day) {
case 1: name = "Monday"; break;
case 2: name = "Tuesday"; break;
default: name = "Other";
}
// Switch Expression (Java 14+):
String name2 = switch (day) {
case 1 -> "Monday";
case 2 -> "Tuesday";
default -> "Other";
};
// Pattern Matching for switch (preview, Java 17):
Object obj = 42;
String result = switch (obj) {
case Integer i -> "Integer: " + i;
case String s -> "String: " + s;
default -> "Unknown";
};The old java.util.Random used a single algorithm, and adding new/better random algorithms was hard (everything was hardcoded into the Random class itself). Java 17 introduced the RandomGenerator interface, giving a common API for all random number generators, old and new.
New algorithms arrived too — like Xoshiro256PlusPlus, which offers better statistical quality and performance than the old Random, especially for parallel/multi-threaded use (like SplittableRandom, which can give independent streams to different threads without lock overhead).
RandomGenerator rng = RandomGenerator.of("Xoshiro256PlusPlus");
System.out.println(rng.nextInt(100)); // 0-99 ke beech ek random number
// Streams of random numbers bhi easily ban sakte hain:
rng.ints(5, 1, 10).forEach(System.out::println); // 5 numbers, 1-9 ke beechThe module system (JPMS) arrived in Java 9, but for backward compatibility, internal JDK APIs (like sun.misc.Unsafe) could still be accessed with the --illegal-access flag. Since Java 17, these internals are strongly encapsulated — they're no longer accessible by default, no matter what flag you use (only a few critical ones need explicit --add-opens).
This is less directly relevant to your own application code, but if a library/framework you use relied on internal JDK classes (via reflection), it could break when upgrading to Java 17 — which is why this is a common gotcha in migration guides.
The Security Manager was an old mechanism that let Java applications run sandboxed (like for browser applets) — restricting what code was allowed to do (file access, network, etc.). Modern applications rarely use it (container/OS-level sandboxing has become much more common), and it was costly for the JDK team to maintain.
In Java 17 it was marked "deprecated for removal" (fully removed in later versions) — if a legacy application uses it, you'll get a warning before it stops working, and you'll eventually need to find an alternative.