🚀
Java 8+

Java 11 Features

var, HTTP Client & New Methods
💡 Java 11 is like a "quality of life" update — it doesn't bring a big new paradigm (like Java 8 did), but makes everyday little tasks easier. Like an old car getting new convenience features — automatic wipers, better AC — the engine's the same, but driving it feels a lot smoother.

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 is like a "quality of life" update — it doesn't bring a big new paradigm (like Java 8 did), but makes everyday little tasks easier. Like an old car getting new convenience features — automatic wipers, better AC — the engine's the same, but driving it feels a lot smoother.
1 / 6
On this page (8 subtopics)

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).

// 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;
⚠️Common Mistake: Mixing isn't allowed — writing (var a, b) -> a + b gives a compile error. Either all parameters use var, or none do.

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.

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"
💡Tip: Think about which one you actually need before choosing isEmpty() vs isBlank() — in form validation (where a user might type only spaces), isBlank() is the more correct check.

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).

// 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!");
⚠️Common Mistake: Files.readString() loads the entire file into memory — risky for very large files (GBs); for large files, stream line-by-line with a BufferedReader instead.

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.

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());
💡Tip: Use the async version (sendAsync) when you need to send multiple requests in parallel, or don't want to block the UI thread — it returns a CompletableFuture<HttpResponse<String>> that you can chain with thenApply()/thenAccept().

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).

// 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!
💡Tip: This feature isn't meant for production deployment — real projects still use a proper build via Maven/Gradle. It's just convenient for quick testing/learning.

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).

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.

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 (cleans up) 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.

GCPurpose
EpsilonNo-op — only allocates, never collects (for testing/measurement)
ZGCLarge heaps, very low pause time (experimental in Java 11)
G1 (default since Java 9+)General-purpose, balanced throughput + pause time