├── java11 ├── src │ └── test │ │ ├── resources │ │ └── HelloWorld.java │ │ └── java │ │ └── org │ │ └── avb │ │ └── whatsnew │ │ └── java11 │ │ ├── optional │ │ └── IsEmptyDemo.java │ │ ├── varinlambdaparams │ │ └── VarsInLambdasDemo.java │ │ ├── collections │ │ └── CollectionsMethodsDemo.java │ │ ├── strings │ │ └── StringsMethodsDemo.java │ │ ├── launchsourcecode │ │ └── LaunchSingleFileProgramDemo.java │ │ └── httpclient │ │ └── HttpClientDemo.java └── pom.xml ├── common ├── src │ └── main │ │ └── resources │ │ └── logback-test.xml └── pom.xml ├── .gitignore ├── java10 ├── src │ └── test │ │ └── java │ │ └── org │ │ └── avb │ │ └── whatsnew │ │ └── java10 │ │ ├── optional │ │ └── OrElseThrowDemo.java │ │ ├── adhocfields │ │ └── AdHocMembersDemo.java │ │ └── typeinference │ │ └── LocalVariableTypeInferenceDemo.java └── pom.xml ├── java8 ├── pom.xml └── src │ └── test │ └── java │ └── org │ └── avb │ └── whatsnew │ └── java8 │ └── methodrefs │ └── MethodReferencesDemo.java ├── java12 ├── pom.xml └── src │ └── test │ └── java │ └── org │ └── avb │ └── whatsnew │ └── java12 │ └── superswitch │ └── SwitchExpressionDemo.java ├── java13 ├── pom.xml └── src │ └── test │ └── java │ └── org │ └── avb │ └── whatsnew │ └── java13 │ ├── textblocks │ └── TextBlockDemo.java │ └── switchexpression │ └── SwitchExpressionDemo.java ├── java9 ├── pom.xml └── src │ └── test │ └── java │ └── org │ └── avb │ └── whatsnew │ └── java9 │ ├── streams │ └── NewStreamMethodsDemo.java │ ├── optional │ └── NewOptionalMethodsDemo.java │ └── collections │ └── CollectionUtilityMethodsDemo.java ├── README.md └── pom.xml /java11/src/test/resources/HelloWorld.java: -------------------------------------------------------------------------------- 1 | public class HelloWorld { 2 | 3 | public static void main(String[] args) { 4 | System.out.println("Hello, world!"); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /common/src/main/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %class{36} %M - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | 11 | # Maven 12 | log/ 13 | target/ 14 | 15 | # Compiled class file 16 | *.class 17 | 18 | # Log file 19 | *.log 20 | 21 | # BlueJ files 22 | *.ctxt 23 | 24 | # Mobile Tools for Java (J2ME) 25 | .mtj.tmp/ 26 | 27 | # Package Files # 28 | *.jar 29 | *.war 30 | *.nar 31 | *.ear 32 | *.zip 33 | *.tar.gz 34 | *.rar 35 | 36 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 37 | hs_err_pid* 38 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | whats-new-in-java 7 | org.avb.whatsnew 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | common 13 | 14 | 15 | -------------------------------------------------------------------------------- /java10/src/test/java/org/avb/whatsnew/java10/optional/OrElseThrowDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java10.optional; 2 | 3 | import org.testng.annotations.Test; 4 | 5 | import java.util.NoSuchElementException; 6 | import java.util.Optional; 7 | 8 | /** 9 | * Since Java 10, the Optional class has orElseThrow that does not have an argument 10 | * and throws NoSuchElementException. 11 | * 12 | * It overloads an already existing method that takes an exception supplier. 13 | */ 14 | public class OrElseThrowDemo { 15 | 16 | @Test(expectedExceptions = NoSuchElementException.class) 17 | public void testOrElseThrow() { 18 | Optional.empty() 19 | .orElseThrow(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /java8/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | whats-new-in-java 7 | org.avb.whatsnew 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | java8 13 | 14 | 15 | 16 | org.avb.whatsnew 17 | common 18 | 19 | 20 | -------------------------------------------------------------------------------- /java10/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | whats-new-in-java 7 | org.avb.whatsnew 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | java10 13 | 14 | 15 | 16 | org.avb.whatsnew 17 | common 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /java11/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | whats-new-in-java 7 | org.avb.whatsnew 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | java11 13 | 14 | 15 | 16 | org.avb.whatsnew 17 | common 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /java12/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | whats-new-in-java 7 | org.avb.whatsnew 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | java12 13 | 14 | 15 | 16 | org.avb.whatsnew 17 | common 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /java13/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | whats-new-in-java 7 | org.avb.whatsnew 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | java13 13 | 14 | 15 | 16 | org.avb.whatsnew 17 | common 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /java9/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | whats-new-in-java 7 | org.avb.whatsnew 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | java9 13 | 14 | 15 | 16 | org.avb.whatsnew 17 | common 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /java11/src/test/java/org/avb/whatsnew/java11/optional/IsEmptyDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java11.optional; 2 | 3 | import org.testng.annotations.Test; 4 | 5 | import java.util.Optional; 6 | 7 | import static org.testng.Assert.*; 8 | 9 | /** 10 | * In Java 11, the Optional class has a small improvement - a new method called isEmpty 11 | * that returns true If a value is not present, otherwise false. 12 | */ 13 | public class IsEmptyDemo { 14 | 15 | @Test 16 | public void testIsEmptyTrue() { 17 | assertTrue(Optional.empty().isEmpty()); // ok 18 | assertTrue(Optional.ofNullable(null).isEmpty()); // ok 19 | } 20 | 21 | @Test 22 | public void testIsEmptyFalse() { 23 | assertFalse(Optional.of(123).isEmpty()); // ok 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /java11/src/test/java/org/avb/whatsnew/java11/varinlambdaparams/VarsInLambdasDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java11.varinlambdaparams; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.annotations.Test; 6 | 7 | import java.lang.annotation.*; 8 | import java.util.List; 9 | 10 | /** 11 | * Java 11 introduces Local-Variable syntax for lambda parameters. 12 | * It is used for one specific case like (@Annotation var param) -> ... 13 | */ 14 | public class VarsInLambdasDemo { 15 | private static final Logger LOGGER = LoggerFactory.getLogger(VarsInLambdasDemo.class); 16 | 17 | @Test 18 | public void testVarInLambdas() { 19 | List.of("Java", "Kotlin", "Scala", "Closure").stream() 20 | .filter((@NotNull var lang) -> lang.startsWith("J")) 21 | .forEach(LOGGER::info); // prints "Java" 22 | } 23 | } 24 | 25 | @Target(ElementType.PARAMETER) 26 | @Inherited 27 | @Retention(RetentionPolicy.CLASS) 28 | @interface NotNull { } -------------------------------------------------------------------------------- /java11/src/test/java/org/avb/whatsnew/java11/collections/CollectionsMethodsDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java11.collections; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.annotations.Test; 6 | 7 | import java.util.*; 8 | 9 | public class CollectionsMethodsDemo { 10 | private static final Logger LOGGER = LoggerFactory.getLogger(CollectionsMethodsDemo.class); 11 | 12 | @Test 13 | public void testToArrayWithGenerator() { 14 | var fibonacci = List.of(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55) 15 | .toArray(Integer[]::new); 16 | 17 | Arrays.stream(fibonacci) 18 | .map(Object::toString) 19 | .forEach(LOGGER::info); // prints items in the same order as in the list 20 | 21 | var languages = Set.of("Java", "Kotlin", "Scala") 22 | .toArray(String[]::new); 23 | 24 | Arrays.stream(languages) 25 | .map(Object::toString) 26 | .forEach(LOGGER::info); // prints items in any order 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /java10/src/test/java/org/avb/whatsnew/java10/adhocfields/AdHocMembersDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java10.adhocfields; 2 | 3 | import org.avb.whatsnew.java10.typeinference.LocalVariableTypeInferenceDemo; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.testng.annotations.Test; 7 | 8 | /** 9 | * Since Java 10 it is possible to expand a class with new fields and methods 10 | * using anonymous classes together with var. 11 | * 12 | * That is not a good practice, but may be used for a reason 13 | */ 14 | public class AdHocMembersDemo { 15 | private static final Logger LOGGER = LoggerFactory.getLogger(AdHocMembersDemo.class); 16 | 17 | @Test 18 | public void testAdHocMembers() { 19 | var intPair = new Object() { 20 | int first = 0; 21 | int second = 0; 22 | 23 | public int sum() { 24 | return first + second; 25 | } 26 | }; 27 | 28 | intPair.first = 10; 29 | intPair.second = 20; 30 | 31 | LOGGER.info("sum of {} and {} is {}", intPair.first, intPair.second, intPair.sum()); 32 | } 33 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What's new in Java 2 | 3 | This project demonstrates new features introduced in different versions of Java (SE). It focuses only on those features that are visible to application developers and may affect the style or approaches to programming in Java. (Re)moving of existing APIs is not considered here at all. 4 | 5 | The project is based on Maven and has a classic multi-modular structure. Each module focuses on a specific version of the language. All examples are located in **test** folders. 6 | 7 | It's in the phase of the active development now. You can help to add some new information with examples by doing a pull request from your fork. 8 | 9 | Follow the link depending on the verion of Java you are interested in: 10 | - [Java13](https://github.com/swsms/whats-new-in-java/tree/master/java13/src/test/java/org/avb/whatsnew/java13) 11 | - [Java12](https://github.com/swsms/whats-new-in-java/tree/master/java12/src/test/java/org/avb/whatsnew/java12) 12 | - [Java11](https://github.com/swsms/whats-new-in-java/tree/master/java11/src/test/java/org/avb/whatsnew/java11) 13 | - [Java10](https://github.com/swsms/whats-new-in-java/tree/master/java10/src/test/java/org/avb/whatsnew/java10) 14 | - [Java9](https://github.com/swsms/whats-new-in-java/tree/master/java9/src/test/java/org/avb/whatsnew/java9) 15 | - [Java8](https://github.com/swsms/whats-new-in-java/tree/master/java8/src/test/java/org/avb/whatsnew/java8) 16 | -------------------------------------------------------------------------------- /java13/src/test/java/org/avb/whatsnew/java13/textblocks/TextBlockDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java13.textblocks; 2 | 3 | import org.testng.annotations.Test; 4 | 5 | import static org.testng.Assert.assertEquals; 6 | 7 | /** 8 | * Java 13 introduces text blocks that simplify the declaration of multi-line strings. 9 | * We do not use need to write newline characters (\n) and mark quotes with backslash (\") 10 | * inside such blocks. 11 | */ 12 | public class TextBlockDemo { 13 | 14 | /* It looks ugly and unreadable! */ 15 | private static final String rawStringHelloWorldProgram = 16 | "public class Main {\n" + 17 | " public static void main(String[] args) {\n" + 18 | " System.out.println(\"Hello, world!\");\n" + 19 | " }\n" + 20 | "}"; 21 | 22 | @Test 23 | public void testTextBlockWithInternalQuotes() { 24 | String helloWorldProgram = """ 25 | public class Main { 26 | public static void main(String[] args) { 27 | System.out.println("Hello, world!"); 28 | } 29 | }"""; 30 | assertEquals(helloWorldProgram, rawStringHelloWorldProgram); 31 | } 32 | 33 | @Test 34 | public void testTextBlockWithMultipleLineBreaks() { 35 | String textWithNumbers = """ 36 | 1 37 | 38 | 2 39 | 40 | 3"""; 41 | assertEquals(textWithNumbers, "1\n\n2\n\n3"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /java9/src/test/java/org/avb/whatsnew/java9/streams/NewStreamMethodsDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java9.streams; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.annotations.Test; 6 | 7 | import java.util.List; 8 | import java.util.stream.Stream; 9 | 10 | public class NewStreamMethodsDemo { 11 | private static final Logger LOGGER = LoggerFactory.getLogger(NewStreamMethodsDemo.class); 12 | 13 | @Test 14 | public void testOfNullable() { 15 | List list = null; 16 | Stream.ofNullable(list) // produces Stream.empty() 17 | .map(Object::toString) 18 | .forEach(LOGGER::info); 19 | } 20 | 21 | @Test 22 | public void testIterateWhileTrue() { 23 | Stream.iterate(0, i -> i < 5, i -> i + 1) // condition: i < 5 24 | .map(Object::toString) 25 | .forEach(LOGGER::info); // 0 1 2 3 4 26 | } 27 | 28 | @Test 29 | public void testTakeWhile() { 30 | List numbers = List.of(2, 4, 6, 8, 9, 10, 12, 14); 31 | numbers.stream() 32 | .takeWhile(n -> n % 2 == 0) 33 | .forEach(n -> LOGGER.info("{}", n)); // 2 4 6 8 34 | } 35 | 36 | @Test 37 | public void testDropWhile() { 38 | List numbers = List.of(2, 4, 6, 8, 9, 10, 12, 14); 39 | numbers.stream() 40 | .dropWhile(n -> n % 2 == 0) 41 | .forEach(n -> LOGGER.info("{}", n)); // 9 10 12 14 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /java13/src/test/java/org/avb/whatsnew/java13/switchexpression/SwitchExpressionDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java13.switchexpression; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.annotations.Test; 6 | 7 | /** 8 | * In Java 13, switch expression can use the yield keyword 9 | * to return values from multi-statement cases. 10 | * Breaks with values are not allowed now. 11 | */ 12 | public class SwitchExpressionDemo { 13 | private static final Logger LOGGER = LoggerFactory.getLogger(SwitchExpressionDemo.class); 14 | 15 | @Test 16 | public void testSwitchExpressionWithYield() { 17 | var a = 10; 18 | var b = 20; 19 | var op = "+"; 20 | 21 | var result = switch (op) { 22 | case "+" -> { 23 | var sum = a + b; 24 | LOGGER.info("Performing {} + {} ...", a, b); 25 | yield sum; 26 | } 27 | case "-" -> { 28 | var diff = a - b; 29 | LOGGER.info("Performing {} - {} ...", a, b); 30 | yield diff; 31 | } 32 | case "*" -> { 33 | var product = a * b; 34 | LOGGER.info("Performing {} * {} ...", a, b); 35 | yield product; 36 | } 37 | default -> { 38 | String errorMsg = String.format("An unknown operation: %s", op); 39 | throw new IllegalArgumentException(errorMsg); 40 | } 41 | }; 42 | 43 | LOGGER.info("The result is {}", result); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /java11/src/test/java/org/avb/whatsnew/java11/strings/StringsMethodsDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java11.strings; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.annotations.Test; 6 | 7 | import static org.testng.Assert.*; 8 | 9 | public class StringsMethodsDemo { 10 | private static final Logger LOGGER = LoggerFactory.getLogger(StringsMethodsDemo.class); 11 | 12 | @Test 13 | public void testRepeatSmile() { 14 | var smiles = ":)".repeat(10); // ":):):):):):):):):):)" 15 | LOGGER.info("smiles: {}", smiles); 16 | } 17 | 18 | @Test 19 | public void testIsBlankString() { 20 | var emptyString = ""; 21 | assertTrue(emptyString.isBlank()); 22 | 23 | var stringWithSpaces = " \t\n"; 24 | assertTrue(stringWithSpaces.isBlank()); 25 | 26 | var stringWithLetters = " abc "; 27 | assertFalse(stringWithLetters.isBlank()); 28 | } 29 | 30 | @Test 31 | public void testLines() { 32 | var languages = "java\nkotlin\nscala\nclosure"; 33 | languages.lines() 34 | .forEach(LOGGER::info); // prints each word from a new line 35 | } 36 | 37 | @Test 38 | public void testStripMethods() { 39 | LOGGER.info("\n\t abc".stripLeading()); // "abc" 40 | LOGGER.info("def\n\t ".stripTrailing()); // "def" 41 | LOGGER.info(" \n\tqq\n\t ".strip()); // "qq" 42 | 43 | var stringWithMagicSpace = "\u205Fabc"; 44 | 45 | LOGGER.info(stringWithMagicSpace.strip()); // "abc" 46 | LOGGER.info(stringWithMagicSpace.trim()); // " abc" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /java8/src/test/java/org/avb/whatsnew/java8/methodrefs/MethodReferencesDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java8.methodrefs; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.annotations.Test; 6 | 7 | import java.time.LocalDate; 8 | import java.util.function.Consumer; 9 | import java.util.function.Predicate; 10 | import java.util.function.Supplier; 11 | import java.util.function.UnaryOperator; 12 | 13 | public class MethodReferencesDemo { 14 | private static final Logger LOGGER = LoggerFactory.getLogger(MethodReferencesDemo.class); 15 | 16 | @Test 17 | public void testUnaryOperatorMethodReference() { 18 | UnaryOperator function = String::toUpperCase; 19 | 20 | LOGGER.info(function.apply("hi")); // HI 21 | } 22 | 23 | @Test 24 | public void testConsumerMethodReference() { 25 | Consumer infoLogger = LOGGER::info; 26 | 27 | infoLogger.accept("hi"); // hi 28 | } 29 | 30 | @Test 31 | public void testConstructorMethodReference() { 32 | Supplier emptyStringCreator = String::new; 33 | 34 | LOGGER.info(emptyStringCreator.get()); // "" 35 | } 36 | 37 | @Test 38 | public void testStaticMethodSupplierMethodReference() { 39 | Supplier emptyStringCreator = LocalDate::now; 40 | 41 | LOGGER.info("{}", emptyStringCreator.get()); // a date like 2020-06-11 42 | } 43 | 44 | @Test 45 | public void testPredicateMethodReference() { 46 | Predicate isUpper = Character::isUpperCase; 47 | 48 | LOGGER.info("{}", isUpper.test('Q')); // true 49 | LOGGER.info("{}", isUpper.test('a')); // false 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /java11/src/test/java/org/avb/whatsnew/java11/launchsourcecode/LaunchSingleFileProgramDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java11.launchsourcecode; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.annotations.Test; 6 | 7 | import java.io.IOException; 8 | import java.net.URISyntaxException; 9 | import java.nio.file.Paths; 10 | 11 | /** 12 | * Since Java 11, it is possible to launch a single source code file invoking the command: 13 | * java HelloWorld.java 14 | * The launcher will compile the source code it before the start. 15 | * 16 | * In this example, a new process for the launcher is created programmatically. 17 | */ 18 | public class LaunchSingleFileProgramDemo { 19 | private static final Logger LOGGER = LoggerFactory.getLogger(LaunchSingleFileProgramDemo.class); 20 | 21 | private final static String FILE_NAME = "HelloWorld.java"; 22 | 23 | @Test 24 | public void testLaunchHelloWorld() throws IOException, URISyntaxException, InterruptedException { 25 | var pathToSourceCode = getPathToSourceCodeFile(); 26 | LOGGER.info("Loaded source code: {}", pathToSourceCode); 27 | 28 | var process = startProcess("java " + pathToSourceCode); 29 | int code = process.waitFor(); 30 | 31 | LOGGER.info("Process code: {}", code); 32 | } 33 | 34 | private String getPathToSourceCodeFile() throws URISyntaxException { 35 | var uri = getClass().getClassLoader().getResource(FILE_NAME); 36 | return Paths.get(uri.toURI()).toString(); 37 | } 38 | 39 | private Process startProcess(String commandWithOptions) throws IOException { 40 | return new ProcessBuilder() 41 | .command(commandWithOptions.split("\\s+")) 42 | .inheritIO() 43 | .start(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /java9/src/test/java/org/avb/whatsnew/java9/optional/NewOptionalMethodsDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java9.optional; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.annotations.Test; 6 | 7 | import java.util.Optional; 8 | 9 | public class NewOptionalMethodsDemo { 10 | private static final Logger LOGGER = LoggerFactory.getLogger(NewOptionalMethodsDemo.class); 11 | 12 | @Test 13 | public void testIfPresentOfElse() { 14 | User user1 = new User("stackman"); 15 | 16 | /* Login: stackman */ 17 | Optional.of(user1) // present 18 | .ifPresentOrElse( 19 | u -> LOGGER.info("Login: {}", u.login), 20 | () -> LOGGER.info("Not present")); 21 | 22 | User user2 = null; 23 | 24 | /* User not present */ 25 | Optional.ofNullable(user2) // not present 26 | .ifPresentOrElse( 27 | u -> LOGGER.info("Login: {}", u.login), 28 | () -> LOGGER.info("User not present")); 29 | } 30 | 31 | @Test 32 | public void testOr() { 33 | User user1 = new User("stackman"); 34 | 35 | /* Optional[User{login='stackman'}] */ 36 | LOGGER.info("{}", 37 | Optional.of(user1) 38 | .or(() -> Optional.of(new User("default")))); 39 | 40 | User user2 = null; 41 | 42 | /* Optional[User{login='default'}] */ 43 | LOGGER.info("{}", 44 | Optional.ofNullable(user2) 45 | .or(() -> Optional.of(new User("default")))); 46 | } 47 | 48 | @Test 49 | public void testToStream() { 50 | User user1 = new User("stackman"); 51 | 52 | /* stackman */ 53 | Optional.of(user1) 54 | .stream() 55 | .map(u -> u.login) 56 | .forEach(LOGGER::info); 57 | 58 | /* empty stream */ 59 | long count = Optional.empty() 60 | .stream() 61 | .count(); 62 | 63 | LOGGER.info("The number of elements: {}", count); // 0 64 | } 65 | } 66 | 67 | class User { 68 | final String login; 69 | 70 | User(String login) { 71 | this.login = login; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return "User{" + 77 | "login='" + login + '\'' + 78 | '}'; 79 | } 80 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.avb.whatsnew 8 | whats-new-in-java 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | 13 | common 14 | java13 15 | java12 16 | java11 17 | java10 18 | java9 19 | java8 20 | 21 | 22 | 23 | UTF-8 24 | 13 25 | 13 26 | 27 | 28 | 29 | 30 | 31 | org.avb.whatsnew 32 | common 33 | ${project.version} 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.testng 41 | testng 42 | 6.14.3 43 | test 44 | 45 | 46 | org.slf4j 47 | slf4j-api 48 | 1.7.25 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ch.qos.logback 58 | logback-classic 59 | 1.2.3 60 | 61 | 62 | ch.qos.logback 63 | logback-core 64 | 1.2.3 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | maven-compiler-plugin 73 | 74 | 75 | --enable-preview 76 | 77 | 78 | 79 | 80 | maven-surefire-plugin 81 | 82 | --enable-preview 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /java10/src/test/java/org/avb/whatsnew/java10/typeinference/LocalVariableTypeInferenceDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java10.typeinference; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.annotations.Test; 6 | 7 | import java.io.*; 8 | import java.time.LocalDate; 9 | import java.util.List; 10 | import java.util.TreeMap; 11 | 12 | import static java.util.stream.Collectors.*; 13 | 14 | /** 15 | * Java 10 extends type inference for local variables using var. 16 | * This feature totally reduces the number of boilerplate code. 17 | * 18 | * Important, this type inference is available only for local variables with the initializer. 19 | * It does not work for member variables, method parameters, return types. 20 | * It also does not work without initialization. 21 | */ 22 | public class LocalVariableTypeInferenceDemo { 23 | private static final Logger LOGGER = LoggerFactory.getLogger(LocalVariableTypeInferenceDemo.class); 24 | 25 | @Test 26 | public void testTypeInferenceInSimpleCases() { 27 | var zero = 0; // int 28 | var one = 1L; // long 29 | 30 | LOGGER.info("zero is denoted as {}, one is denoted as {}", zero, one); 31 | 32 | var msg = "How are you today?"; // String 33 | var today = LocalDate.now(); // LocalDate 34 | 35 | LOGGER.info("{}: {}", today, msg); 36 | } 37 | 38 | @Test 39 | public void testTypeInferenceWithGenerics() { 40 | var persons = List.of( // var replaces List 41 | new Person("John", 34), 42 | new Person("Margo", 28), 43 | new Person("Alex", 18), 44 | new Person("John", 22), 45 | new Person("Katy", 34), 46 | new Person("Paul", 28) 47 | ); 48 | 49 | LOGGER.info("Persons: {}", persons); 50 | 51 | var namesByAges = persons.stream() // var replaces Map> 52 | .collect( 53 | groupingBy( 54 | Person::getAge, 55 | TreeMap::new, 56 | mapping(Person::getName, 57 | toList()))); 58 | 59 | namesByAges.forEach((age, names) -> LOGGER.info("{}: {}", age, names)); 60 | 61 | /* Sorted output cause we use TreeMap 62 | * 18: [Alex] 63 | * 22: [John] 64 | * 28: [Margo, Paul] 65 | * 34: [John, Katy] 66 | */ 67 | } 68 | 69 | @Test 70 | public void testTypeInferenceWithTryWithResources() throws IOException { 71 | var bytes = new byte[] { 0x01, 0x02, 0x03 }; 72 | 73 | try (var output = new ByteArrayOutputStream(); 74 | var input = new ByteArrayInputStream(bytes)) { 75 | LOGGER.info("Inside try-with-resources"); 76 | input.transferTo(output); 77 | } 78 | } 79 | } 80 | 81 | class Person { 82 | private final String name; 83 | private final int age; 84 | 85 | Person(String name, int age) { 86 | this.name = name; 87 | this.age = age; 88 | } 89 | 90 | public String getName() { 91 | return name; 92 | } 93 | 94 | public int getAge() { 95 | return age; 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | return "Person{" + 101 | "name='" + name + '\'' + 102 | ", age=" + age + 103 | '}'; 104 | } 105 | } -------------------------------------------------------------------------------- /java12/src/test/java/org/avb/whatsnew/java12/superswitch/SwitchExpressionDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java12.superswitch; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.Assert; 6 | import org.testng.annotations.Test; 7 | 8 | import static org.testng.Assert.*; 9 | 10 | /** 11 | * In Java 12, the old switch statement was drastically improved. 12 | * As you remember, the old form of this statement looked old fashioned (not like in Kotlin/Scala :)) 13 | * and, more importantly, it could lead to errors due to the lack of breaks. 14 | * Now, it supports multiple values in cases, allows you do not use breaks, 15 | * can be used an expression and has a nice arrow lambda-like syntax (->) in addition to old colon (:). 16 | */ 17 | public class SwitchExpressionDemo { 18 | private static final Logger LOGGER = LoggerFactory.getLogger(SwitchExpressionDemo.class); 19 | 20 | /** Just to avoid some warnings in examples */ 21 | private int getDayNumber() { 22 | return 6; 23 | } 24 | 25 | /** 26 | * Here is an old switch statement which exists from beginning. 27 | * Just to remember. 28 | */ 29 | @Test 30 | public void testTraditionalSwitchStatement() { 31 | String dayOfWeekName; 32 | 33 | switch (getDayNumber()) { 34 | case 1: 35 | dayOfWeekName = "Monday"; 36 | break; 37 | case 2: 38 | dayOfWeekName = "Tuesday"; 39 | break; 40 | case 3: 41 | dayOfWeekName = "Wednesday"; 42 | break; 43 | case 4: 44 | dayOfWeekName = "Thursday"; 45 | break; 46 | case 5: 47 | dayOfWeekName = "Friday"; 48 | break; 49 | case 6: 50 | dayOfWeekName = "Saturday"; 51 | break; 52 | case 7: 53 | dayOfWeekName = "Sunday"; 54 | break; 55 | default: 56 | dayOfWeekName = "An unknown day"; 57 | } 58 | 59 | LOGGER.info(dayOfWeekName); 60 | } 61 | 62 | /** 63 | * Here is a new switch statement with the nice arrow syntax. 64 | * Now it looks more compact and less boilerplate. 65 | * It's hard to make a mistake forgetting breaks. 66 | */ 67 | @Test 68 | public void testModernSwitchStatementWithArrowSyntax() { 69 | String dayOfWeekName; 70 | 71 | switch (getDayNumber()) { 72 | case 1 -> dayOfWeekName = "Monday"; 73 | case 2 -> dayOfWeekName = "Tuesday"; 74 | case 3 -> dayOfWeekName = "Wednesday"; 75 | case 4 -> dayOfWeekName = "Thursday"; 76 | case 5 -> dayOfWeekName = "Friday"; 77 | case 6 -> dayOfWeekName = "Saturday"; 78 | case 7 -> dayOfWeekName = "Sunday"; 79 | default -> dayOfWeekName = "An unknown day"; 80 | } 81 | 82 | LOGGER.info(dayOfWeekName); 83 | } 84 | 85 | /** 86 | * Here is a switch expression which allows you to return the result from cases 87 | * and avoid duplicate assignments. It looks fantastic! 88 | */ 89 | @Test 90 | public void testSwitchExpressionWithArrowSyntax() { 91 | var dayOfWeekName = switch (getDayNumber()) { 92 | case 1 -> "Monday"; 93 | case 2 -> "Tuesday"; 94 | case 3 -> "Wednesday"; 95 | case 4 -> "Thursday"; 96 | case 5 -> "Friday"; 97 | case 6 -> "Saturday"; 98 | case 7 -> "Sunday"; 99 | default -> "An unknown day"; 100 | }; 101 | 102 | LOGGER.info(dayOfWeekName); 103 | } 104 | 105 | /** 106 | * Another interesting thing, that switch (both statement and expression) 107 | * supports multiple labels in cases. 108 | * See the calculate method below. 109 | */ 110 | @Test 111 | public void testSwitchWithMultipleLabelsInCases() { 112 | assertEquals(calculate(3, 5, "+"), 8); 113 | assertEquals(calculate(4, 2, "plus"), 6); 114 | assertEquals(calculate(3, 3, "*"), 9); 115 | assertEquals(calculate(2, 3, "multiply"), 6); 116 | // and so on 117 | } 118 | 119 | private int calculate(int a, int b, String op) { 120 | return switch (op) { 121 | case "+", "plus" -> a + b; 122 | case "-", "minus" -> a - b; 123 | case "*", "mult", "multiply" -> a * b; 124 | default -> { 125 | String errorMsg = String.format("An unknown operation: %s", op); 126 | throw new IllegalArgumentException(errorMsg); 127 | } 128 | }; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /java9/src/test/java/org/avb/whatsnew/java9/collections/CollectionUtilityMethodsDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java9.collections; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.annotations.Test; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Map.Entry; 10 | import java.util.Set; 11 | 12 | /** 13 | * Before Java 9 we could write Arrays.asList(...) to get a list with the specified elements. 14 | * We could also use methods like unmodifiableXXX, singletonXXX or emptyXXX to create unmodifiable lists, sets and maps. 15 | * But these methods are very tedious and verbose. 16 | * 17 | * Since Java 9 there is a set of convenient methods for creating immutable collections with the specified elements. 18 | * It returns an optimized implementation of a collection depending on the number of elements 19 | */ 20 | public class CollectionUtilityMethodsDemo { 21 | private static final Logger LOGGER = LoggerFactory.getLogger(CollectionUtilityMethodsDemo.class); 22 | 23 | @Test 24 | public void testListOf() { 25 | List emptyList = List.of(); 26 | LOGGER.info("{}", emptyList); // [] 27 | 28 | List singletonList = List.of("Orange"); 29 | LOGGER.info("{}", singletonList); // [Orange] 30 | 31 | List currencies = List.of("EUR", "USD", "CAD", "CNY"); 32 | LOGGER.info("{}", currencies); // [EUR, USD, CAD, CNY] 33 | } 34 | 35 | @Test 36 | public void testSetOf() { 37 | Set emptySet = Set.of(); 38 | LOGGER.info("{}", emptySet); // [] 39 | 40 | Set singletonList = Set.of("Orange"); 41 | LOGGER.info("{}", singletonList); // [Orange] 42 | 43 | Set currencies = Set.of("EUR", "USD", "CAD", "CNY"); 44 | LOGGER.info("{}", currencies); // Order can be any: [USD, CAD, CNY, EUR] 45 | } 46 | 47 | @Test 48 | public void testMapOf() { 49 | Map emptyMap = Map.of(); 50 | LOGGER.info("{}", emptyMap); // {} 51 | 52 | Map oneAddress = Map.of("google.com", "172.217.22.174"); 53 | LOGGER.info("{}", oneAddress); // {google.com=172.217.22.174} 54 | 55 | /* The format is: key, value, key, value, ... */ 56 | Map addresses = Map.of( 57 | "google.com", "172.217.22.174", 58 | "github.com", "192.30.253.112", 59 | "stackoverflow.com", "151.101.129.69" 60 | ); 61 | 62 | /* {google.com=172.217.22.174, stackoverflow.com=151.101.129.69, github.com=192.30.253.112} */ 63 | LOGGER.info("{}", addresses); 64 | } 65 | 66 | @Test(expectedExceptions = NullPointerException.class) 67 | public void testNullNotAllowed() { 68 | List digits = List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, null); 69 | } 70 | 71 | @Test(expectedExceptions = UnsupportedOperationException.class) 72 | public void testImmutability() { 73 | List digits = List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); 74 | digits.add(100); // it throws an exception 75 | } 76 | 77 | @Test 78 | public void testMapOfEntries() { 79 | Map emptyMap = Map.ofEntries(); 80 | LOGGER.info("{}", emptyMap); // {} 81 | 82 | Entry nameToPhone1 = Map.entry("Darryl C. Boyd","1-651-643-4993"); 83 | Entry nameToPhone2 = Map.entry("Irene R. Jenkins","1-651-643-4993"); 84 | LOGGER.info("{}", nameToPhone2); // Irene R. Jenkins=1-651-643-4993 85 | 86 | Map nameToPhone = Map.ofEntries(nameToPhone1, nameToPhone2); 87 | 88 | /* {Darryl C. Boyd=1-651-643-4993, Irene R. Jenkins=1-651-643-4993} */ 89 | LOGGER.info("{}", nameToPhone); 90 | } 91 | 92 | @Test 93 | public void testListOfSingleElementWithClass() { 94 | List singleLanguageList = List.of("Java"); 95 | 96 | /* java.util.ImmutableCollections$List12: [Java] */ 97 | LOGGER.info("{}: {}", singleLanguageList.getClass(), singleLanguageList); 98 | } 99 | 100 | @Test 101 | public void testListOfTwoElementsWithClass() { 102 | List twoLanguagesList = List.of("Java", "Kotlin"); 103 | 104 | /* java.util.ImmutableCollections$List12: [Java, Kotlin] */ 105 | LOGGER.info("{}: {}", twoLanguagesList.getClass(), twoLanguagesList); 106 | } 107 | 108 | @Test 109 | public void testListOfThreeElementsWithClass() { 110 | List threeLanguagesList = List.of("Java", "Kotlin", "Scala"); 111 | 112 | /* java.util.ImmutableCollections$ListN: [Java, Kotlin, Scala] */ 113 | LOGGER.info("{}: {}", threeLanguagesList.getClass(), threeLanguagesList); 114 | } 115 | 116 | @Test 117 | public void testListOfNElementsWithClass() { 118 | List fibSequencePart = List.of(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89); 119 | 120 | /* java.util.ImmutableCollections$ListN: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] */ 121 | LOGGER.info("{}: {}", fibSequencePart.getClass(), fibSequencePart); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /java11/src/test/java/org/avb/whatsnew/java11/httpclient/HttpClientDemo.java: -------------------------------------------------------------------------------- 1 | package org.avb.whatsnew.java11.httpclient; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.annotations.DataProvider; 6 | import org.testng.annotations.Test; 7 | 8 | import java.io.IOException; 9 | import java.net.Authenticator; 10 | import java.net.PasswordAuthentication; 11 | import java.net.URI; 12 | import java.net.http.HttpClient; 13 | import java.net.http.HttpRequest; 14 | import java.net.http.HttpResponse; 15 | import java.net.http.WebSocket; 16 | import java.time.Duration; 17 | import java.util.concurrent.*; 18 | 19 | /** 20 | * Java 11 standardizes a new http client introduced in Java 9. 21 | * It supports HTTP/1.1, HTTP/2, WebSockets, and both sync and async requests. 22 | * 23 | * By default the client will send requests using HTTP/2. 24 | * If the server does not support HTTP/2 yet, the request will be downgraded to HTTP/1.1. 25 | * 26 | * HTTP/2 key features: 27 | * - header compression, 28 | * - single connection for parallel requests, 29 | * - fully multiplexed, 30 | * - server push, 31 | * - more compact binary format. 32 | * 33 | * More about HTTP/2: https://http2.github.io/faq/#what-are-the-key-differences-to-http1x 34 | */ 35 | public class HttpClientDemo { 36 | private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientDemo.class); 37 | private static final Duration REQUEST_DURATION = Duration.ofSeconds(60); 38 | 39 | @DataProvider(name = "httpResourcesProvider") 40 | public static Object[][] resources() { 41 | return new Object[][] { 42 | { "https://http2.github.io" }, 43 | { "https://github.io" } 44 | }; 45 | } 46 | 47 | @Test(dataProvider = "httpResourcesProvider") 48 | public void testSyncGetRequest(String url) throws InterruptedException { 49 | var request = HttpRequest.newBuilder() 50 | .uri(URI.create(url)) 51 | .timeout(REQUEST_DURATION) 52 | .GET() // default 53 | .build(); 54 | 55 | var httpClient = HttpClient.newHttpClient(); 56 | 57 | try { 58 | var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); 59 | printResponse(response, false); 60 | } catch (IOException e) { 61 | LOGGER.error(e.getMessage(), e); 62 | } 63 | } 64 | 65 | @Test 66 | public void testSyncPostRequest() throws InterruptedException { 67 | var fakePostService = URI.create("https://jsonplaceholder.typicode.com/posts"); 68 | 69 | var request = HttpRequest.newBuilder() 70 | .uri(fakePostService) 71 | .timeout(REQUEST_DURATION) 72 | .header("Content-Type", "application/json") 73 | .POST(HttpRequest.BodyPublishers.ofString("{\"title\":\"foo\", \"body\":\"bar\"}")) 74 | .build(); 75 | 76 | var httpClient = HttpClient.newHttpClient(); 77 | 78 | try { 79 | var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); 80 | printResponse(response, true); 81 | } catch (IOException e) { 82 | LOGGER.error(e.getMessage(), e); 83 | } 84 | } 85 | 86 | @Test(dataProvider = "httpResourcesProvider") 87 | public void testAsyncGetRequest(String url) { 88 | var request = HttpRequest.newBuilder() 89 | .uri(URI.create(url)) 90 | .timeout(REQUEST_DURATION) 91 | .GET() 92 | .build(); 93 | 94 | var httpClient = HttpClient.newHttpClient(); 95 | 96 | httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) 97 | .thenAccept(response -> printResponse(response, false)) 98 | .join(); 99 | } 100 | 101 | @Test 102 | public void testWebSocket() throws InterruptedException { 103 | var echoServerURI = URI.create("wss://echo.websocket.org"); 104 | var executor = Executors.newSingleThreadExecutor(); 105 | var httpClient = HttpClient.newBuilder() 106 | .executor(executor) 107 | .build(); 108 | 109 | var webSocket = httpClient.newWebSocketBuilder().buildAsync(echoServerURI, new WebSocket.Listener() { 110 | 111 | @Override 112 | public void onOpen(WebSocket webSocket) { 113 | LOGGER.info("Successfully connected to {}", echoServerURI); 114 | webSocket.sendText("Hello!", true); 115 | WebSocket.Listener.super.onOpen(webSocket); 116 | } 117 | 118 | @Override 119 | public CompletionStage onText(WebSocket webSocket, CharSequence data, boolean last) { 120 | LOGGER.info("received: {}", data); 121 | return WebSocket.Listener.super.onText(webSocket, data, last); 122 | } 123 | 124 | @Override 125 | public CompletionStage onClose(WebSocket webSocket, int statusCode, String reason) { 126 | LOGGER.info("Closed with status " + statusCode + ", reason: " + reason); 127 | executor.shutdown(); 128 | return WebSocket.Listener.super.onClose(webSocket, statusCode, reason); 129 | } 130 | }).join(); 131 | 132 | Thread.sleep(1000); 133 | 134 | webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok") 135 | .thenRun(() -> LOGGER.info("Sent close")); 136 | } 137 | 138 | @Test 139 | public void testBasicAuth() throws InterruptedException { 140 | var request = HttpRequest.newBuilder() // GET by default 141 | .uri(URI.create("https://postman-echo.com/basic-auth")) 142 | .timeout(REQUEST_DURATION) 143 | .build(); 144 | 145 | var authClient = HttpClient 146 | .newBuilder() 147 | .authenticator(new Authenticator() { 148 | @Override 149 | protected PasswordAuthentication getPasswordAuthentication() { 150 | return new PasswordAuthentication("postman", "password".toCharArray()); 151 | } 152 | }) 153 | .build(); 154 | 155 | try { 156 | var response = authClient.send(request, HttpResponse.BodyHandlers.ofString()); 157 | printResponse(response, true); 158 | } catch (IOException e) { 159 | LOGGER.error(e.getMessage(), e); 160 | } 161 | } 162 | 163 | 164 | @Test 165 | public void testHandleBodyAsReactiveStream() throws InterruptedException { 166 | var request = HttpRequest.newBuilder() 167 | .uri(URI.create("http://emojitrack-gostreamer.herokuapp.com/subscribe/eps")) 168 | .timeout(REQUEST_DURATION) 169 | .GET() 170 | .build(); 171 | 172 | var httpClient = HttpClient.newHttpClient(); 173 | 174 | // TODO subscriber 175 | // TODO send async 176 | } 177 | 178 | 179 | private static void printResponse(HttpResponse response, boolean printBody) { 180 | LOGGER.info("Protocol version: {}", response.version()); 181 | LOGGER.info("Status code: {}", response.statusCode()); 182 | response.headers() 183 | .map() 184 | .forEach((name, values) -> LOGGER.info(name + ": " + values)); 185 | LOGGER.info("Body length {}", response.body().length()); 186 | if (printBody) { 187 | LOGGER.info("Body {}", response.body()); 188 | } 189 | } 190 | } --------------------------------------------------------------------------------