Java 11 Features
Java 11 (September 2018) is an LTS (Long-Term Support) release — meaning companies trust it in production, since Oracle supports it for a long time (the most popular LTS version after Java 8).
It brought several small but useful additions: new String methods (isBlank, strip, lines, repeat), easier file I/O with Files.readString()/writeString(), a new standard HTTP Client API, and the ability to run a single-file Java program directly without compiling it first.
In interviews, Java 11 features often come up in follow-ups like "what's new after Java 8" — especially var in lambdas, the new String methods, and the HTTP Client API.
// Naye String methods
System.out.println(" ".isBlank()); // true
System.out.println("Hi".repeat(3)); // HiHiHi
// Files.readString() — ek line mein file padho
String content = Files.readString(Paths.get("notes.txt"));
// var in lambda parameters
BiFunction<Integer, Integer, Integer> add = (var a, var b) -> a + b;- Java 11 = LTS release (Sept 2018)
- var can be used in lambda parameters
- isBlank/strip/lines/repeat = new String methods
- java.net.http = the new standard HTTP Client
- Single-file .java programs can run without compiling
Java 10 introduced "var" for local variables (var x = 10;), but you couldn't use it in lambda parameters. Java 11 filled that gap — now you can write var in a lambda's parameters too, like (var a, var b) -> a + b.
The benefit shows up when you need to put an annotation on a parameter (like @NotNull var x) — without var, putting an annotation on a lambda parameter wasn't syntactically possible. Writing just the name, (a, b) -> ..., and writing var, (var a, var b) -> ..., compile to exactly the same thing — no performance difference, this feature exists purely for annotation support.
Rule: either write var on all the parameters, or on none of them — you can't mix, so (var a, b) -> ... is invalid (a compile error).
It might come up in an interview whether var is a "dynamic typing" feature — it's not, Java is still statically typed; var just lets the compiler infer the type at compile time, nothing changes at runtime.
- Without var: (a, b) -> a + b — short, good enough for most cases
- With var: (var a, var b) -> a + b — needed for annotations
- Mixing is invalid: (var a, b) -> ... gives a compile error
// Normal lambda
BiFunction<Integer, Integer, Integer> add1 = (a, b) -> a + b;
// var ke saath (Java 11+)
BiFunction<Integer, Integer, Integer> add2 = (var a, var b) -> a + b;
// Annotation ke saath — sirf var se hi possible
// (var a, @Deprecated var b) -> a + b;isBlank() checks whether a String is empty or contains only whitespace — isEmpty() only checks if the length is 0, but for " " (just spaces), isEmpty() returns false while isBlank() returns true. This was a common source of bugs before Java 11.
strip()/stripLeading()/stripTrailing() work like trim(), but are Unicode-aware — trim() only understands ASCII whitespace (characters with a code less than or equal to space), while strip() also handles Unicode whitespace (like non-breaking spaces). Modern code prefers strip() over trim().
lines() splits a String into a Stream<String>, line by line (useful for multi-line text, without manually calling split("\n")). repeat(n) builds a new String by repeating a String n times.
A real-world use-case: in form input validation, isBlank() instantly tells you whether a user typed something meaningful or just spaces — something that previously needed a manual trim().isEmpty().
- isBlank() — checks if it's only whitespace
- strip()/stripLeading()/stripTrailing() — Unicode-aware trim
- lines() — splits a String into a Stream, line by line
- repeat(n) — repeats a String n times
String s = " ";
System.out.println(s.isEmpty()); // false (length > 0 hai)
System.out.println(s.isBlank()); // true (sirf whitespace hai)
String text = "Line1\nLine2\nLine3";
text.lines().forEach(System.out::println); // 3 lines print
System.out.println("Ab".repeat(3)); // "AbAbAb"Before Java 11, reading a whole file into a String meant calling Files.readAllBytes() and manually doing new String(bytes, charset), or collecting a stream from Files.lines() — a bit of boilerplate. Files.readString(path) does all of this in one line.
Files.writeString(path, content) similarly writes a String straight to a file, without manually converting to bytes. Both methods default to UTF-8 (an overload also lets you pass a custom Charset).
These methods are great for small config files, templates, or test fixtures — for large data files or log processing, NIO's stream-based APIs (like Files.lines()) are still the better fit.
// Purana tareeka (Java 11 se pehle):
byte[] bytes = Files.readAllBytes(Paths.get("notes.txt"));
String content = new String(bytes, StandardCharsets.UTF_8);
// Java 11+:
String content2 = Files.readString(Paths.get("notes.txt"));
Files.writeString(Paths.get("output.txt"), "Hello Java 11!");It arrived as an incubator (experimental) module in Java 9, and became the standard java.net.http package in Java 11 — a modern replacement for the old HttpURLConnection (which dates back to Java 1.1, and is clunky and limited).
The new client supports HTTP/2 (by default), gives you both synchronous (send()) and asynchronous (sendAsync(), which returns a CompletableFuture) ways to make requests, and uses a fluent Builder pattern to build requests — reducing the need for third-party libraries (like Apache HttpClient or OkHttp) for simple use-cases.
In a microservices architecture, where one service calls another over REST, this built-in client gets the job done without adding a third-party dependency — especially handy in smaller projects where pulling in a whole library feels like overkill.
- HTTP/2 support by default
- send() — synchronous, waits for the response
- sendAsync() — asynchronous, returns a CompletableFuture
- Build requests with a fluent Builder pattern
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());Normally in Java you'd first compile with javac to create a .class file, then run it with java. Since Java 11, you can run a single .java file directly with the java command, without an explicit compile step — java Hello.java. The JVM compiles it in the background and runs it directly in memory (no .class file gets written to disk).
This feature is only for single-file programs (not multi-file projects) — it's very useful for quick scripts, prototyping, or learning, since one command replaces the usual two (javac + java).
This is especially handy for coding interviews and competitive programming — you can write code directly for an online judge or a quick script without ever setting up a project.
// Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Direct run, no compile step!");
}
}
// Terminal mein:
// $ java Hello.java
// Direct run, no compile step!Collection.toArray(IntFunction<T[]> generator) is a new overload — previously you'd write something like toArray(new String[0]), now you can use a cleaner method reference like toArray(String[]::new), where the collection itself figures out the array size.
Optional.isEmpty() was added — previously there was only isPresent(), so checking "if there's no value" meant writing !optional.isPresent(). isEmpty() is more readable (avoids the negation).
toArray(String[]::new) is better than the old toArray(new String[0]) because the JIT compiler can intrinsify this pattern, giving a small performance boost on large collections too — a real payoff from a tiny syntax change.
List<String> names = List.of("Riya", "Aman");
String[] arr = names.toArray(String[]::new); // naya, cleaner tareeka
Optional<String> opt = Optional.empty();
if (opt.isEmpty()) {
System.out.println("Koi value nahi hai");
}Sometimes you need to negate an existing Predicate (like String::isBlank) — you can't negate a method reference directly (String::isBlank has no negate() to call on it directly), so previously you had to write a lambda instead: s -> !s.isBlank().
Predicate.not(predicate) is a static helper that cleans this up — Predicate.not(String::isBlank) directly negates the method reference, without writing an extra lambda.
With a static import (import static java.util.function.Predicate.not;), it gets even cleaner — you can drop the "Predicate." prefix entirely and just write not(String::isBlank).
List<String> words = List.of("Java", "", " ", "Code");
// Java 11 se pehle:
words.stream().filter(s -> !s.isBlank()).forEach(System.out::println);
// Java 11+, Predicate.not() ke saath:
words.stream().filter(Predicate.not(String::isBlank)).forEach(System.out::println);Epsilon GC (JEP 318) is a "no-op" (does-nothing) garbage collector — it allocates memory, but never collects any of it. Sounds strange, but it's useful for performance testing (measuring GC's overhead) or very short-lived programs (that finish before they'd ever run out of memory anyway).
ZGC (JEP 333, experimental in this version) is a scalable, low-latency garbage collector that can handle heaps from a few GB up to multiple TB, keeping pause times under 10ms — designed for very large applications where even a small pause is noticeable. For interviews, knowing their names and a one-line purpose is usually enough — deep internals are rarely asked.
If an interviewer asks "when would you use Epsilon GC in production" — the honest answer is: probably never, it's purely a testing/benchmarking tool. For production, G1 (the default) or ZGC for specific latency needs are the practical choices.
| GC | Purpose |
|---|---|
| Epsilon | No-op — only allocates, never collects (for testing/measurement) |
| ZGC | Large heaps, very low pause time (experimental in Java 11) |
| G1 (default since Java 9+) | General-purpose, balanced throughput + pause time |