I work in the banking sector. There are conservative approaches - people prefer to use only technologies that have been tested for years. So we have a lot of codebases written on java 7 and 8. I have provided a couple of workshops speaking of new java features and decided to write an article to combine all valuable new features. I only want to talk about the API I started using myself and find it very helpful. For each new java version, I will give the most valuable features. Let’s get started. In JAVA 9, we got factory methods for collections. Now we can get the unmodifiable collection: For list: List.of(E... elements) For set: Set.of(E... elements) For map: and Map.of(K k1, V v1, K k2, V v2, ...) Map.ofEntries(Entry<? extends K, ? extends V>... entries) So now you can work with an immutable collection more easily without such ugly constructions, like before: List<String> list = ArrayList<>(); list.add( ); list.add( ); Collections.unmodifiableList(list); new "abc" "xyz" You can do it in one line: List.of( , ); "abc" "xyz" Also, it gives us more safety because we increase the number of immutable lists in our program. I use these methods in every my program and strongly recommend you to do so. The following helpful method you can find in this version of Java is: ifPresentOrElse(Consumer<? T> action, Runnable emptyAction) super I try to use Optional class in every place where I can get a null reference. So this method can help us avoid redundant if-else statements: Optional<Email> mail = ... (mail.isPresent()) { send(mail.get()); } { reportError(); } if else You could rewrite it in more flexible manner: opt.ifPresentOrElse( mail -> send(mail), () -> reportError()); With method Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) we can generate a new optional class if the base statement return empties optional: Optional< > optional = Optional.< >ofNullable( ) .or(() -> Optional.of( )); String String "first" "second" Also, we can generate a Stream from the Optional just-only call method . stream() Optional.of(obj).stream() It’s much better than: Optional.of(obj).map(Stream:: ).orElse(Stream.empty()) of Stream interface is similarly getting new methods. So, in Java 8, we had handy intermediate operations like limit and skip. But these operations do not give us enough flexibility to operate with the Stream. From the next release of Java, we have methods: Stream <T> takeWhile(Predicate<? T> predicate) Stream<T> dropWhile(Predicate<? T> predicate) default super default super These methods accept predicate, so you can give some logic on limiting or skip elements, not just by the number of elements. Method iterate also got variant with the predicate. Now, you can write some logic to stop generate objects: IntStream .iterate( , i -> i < , i -> i + ) .forEach(System.out::println); 1 16 1 Instead of: IntStream .iterate( , i -> i + ) .takeWhile(i -> i < ) .forEach(System.out::println); 1 1 16 So, we mentioned streams. Here I should talk about a new method from Java 16 - toList(). We have a list on which we need to perform some transformation and return a new one. You have probably seen this code many times: collection.stream() .collect(Collectors.toList()); // operations In Java 16, this is possible with the new Stream.toList() method: collection.stream() .toList(); // operations It gives us a more convenient code, and thanks to optimization, it has a little bit more performance rate than . collect(Collectors.toList()) I think almost every program works with time. And sometimes, it is a big headache to handle the date and time in your program accurately. Java 8 made a big leap to make it more friendly, but some methods are absent. Java 9 gives us these methods in package. Now you could use Duration class more productive. It got methods , , , etc. You can get “time parts” from the Duration class. There is an example: java.time toDaysPart() toHoursPart() toMinutesPart() toSecondsPart() Duration dur = Duration.between(now().minusDays( ).minusSeconds( ), now()); System.out.println(dur.toDaysPart()); System.out.println(dur.toMinutesPart()); 10 567 // 10 // 9 And now we could even divide time by time! Duration dur = Duration.of( , ChronoUnit.HOURS); Duration ans = dur.dividedBy(Duration.of( , ChronoUnit.MINUTES)); System.out.println(ans); 3 20 // 9 This method is a handy method when the time interval must be divided into segments. In LocalDate, we got new method datesUntil. This method returns a Stream of dates between two dates: Stream<LocalDate> dates = LocalDate.of( , , ).datesUntil(LocalDate.of( , , )); 2021 3 3 2021 10 9 In the 11 version of java, we get a bunch of handful methods for strings. The String.isBlank() method lets you know if the string is composed solely of whitespace: System.out.println( .isBlank()); " \t \n \r" // true , , and methods remove the whitespace characters at the beginning of the string, at the end of the string, or from both ends: stripLeading() stripTrailing() strip() str = ; System.out.println(str.stripLeading()); System.out.println(str.stripTrailing()); System.out.println(str.strip()); String "\tHello, world!\t\n\r" // "Hello, world!\t\n\r" // " \tHello, world!" // "Hello, world!" The repeat() method concatenates the string with itself n times: System.out.println( .repeat( )); "Hello, world!" 3 Java 15 introduces blocks of text - string literals that can consist of one or more lines: str = ; String "" " Hello, World!" "" This feature gives exceptional flexibility, it is not a new API, but I have to mention it because it significantly increases readability. For example, when making JDBC queries or during tests, we can specify the content of a multi-line query in text, and it will be good readable. Also, in this version, the String class got a new method formatted(). So, instead of String.format we could write: .formatted(user); "Hi, %s!" This method also works for text blocks. In Java 11 interface got the default method . With you can replace lambdas with negation with method references: not() Predicate.not() filter(str -> !str.isBlank()) And now using not(): filter(not( ::isBlank)) String Code is more readable, and we don't use any new variables. I really like it! In the end, I want to tell you about the pattern-matching feature. The purpose of this feature is to remove a lot of code that usually accompanies the statement: instanceof (cat Animal) { Animal animal = (Animal) person; animal.voice(); } if instanceof It is a widespread pattern in Java. In 99% percent of cases, we use conversion to type inside if statement. With pattern matching, you can write it more shortly: (cat Animal animal) { animal.voice(); } if instanceof You can do even more: (cat Animal animal && animal.hasVoice()) { animal.voice(); } if instanceof So you can avoid redundant if statement in your code. Thank you! Don't be afraid to use new versions of java. With each new version, it gives us lighter and better-looking code.