Comparison of Java 8 vs Java 17, Should You Upgrade?

What changes made after Java 8?

We will see all the new features added by Java after version 8. These are Pattern Matching, Records, Sealed Classes, Switch Expressions, Text Blocks. Some of these features were announced in previous JDK versions as a preview or finalized.

public class OldJavaInstanceOf {


public static void main(String[] args) {

System.out.println(formatter("Java 17"));
System.out.println(formatter(17));

}

static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}

}
O/P
======
String Java 17
int 17
  • Uses “->” instead of “:”
  • Allows multiple constants per case.
  • Does not have fall-through semantics (i.e., Does not require breaks).
  • Makes variables defined inside a case branch local to this branch.
  • A “default” branch has to be provided.
public class PatternMatchingExample {

public static void main(String[] args) {

System.out.println(formatterJava17("Java 17"));
System.out.println(formatterJava17(17));

}

static String formatterJava17(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}

}
O/P
======
String Java 17
int 17
public class OldPatterNullCheck {

public static void main(String[] args) {

testString("Java 16"); // Ok
testString("Java 11"); // LTS
testString(""); // Ok
testString(null); // Unknown!
}

static void testString(String s) {
if (s == null) {
System.out.println("Unknown!");
return;
}
switch (s) {
case "Java 11", "Java 17" -> System.out.println("LTS");
default -> System.out.println("Ok");
}
}

}
public class Java17PatterNullCheck {
public static void main(String[] args) {

testStringJava17("Java 16"); // Ok
testStringJava17("Java 11"); // LTS
testStringJava17(""); // Ok
testStringJava17(null); // Unknown!
}

static void testStringJava17(String s) {
switch (s) {
case null -> System.out.println("Unknown!");
case "Java 11", "Java 17" -> System.out.println("LTS");
default -> System.out.println("Ok");
}
}
}
class Shape {}
class Rectangle extends Shape {}
class Triangle extends Shape {
int calculateArea(){
//...
} }

static void testTriangle(Shape s) {
switch (s) {
case null:
break;
case Triangle t:
if (t.calculateArea() > 100) {
System.out.println("Large triangle");
break;
}else{
System.out.println("Triangle");
}
default:
System.out.println("Unknown!");
}
}
static void testTriangle2(Shape s) {
switch (s) {
case null ->
{}
case Triangle t && (t.calculateArea() > 100) ->
System.out.println("Large triangle");
case Triangle t ->
System.out.println("Triangle");
default ->
System.out.println("Unknown!");
}
}
/**
* A text block can be declared by starting with three double quotes """ which should be followed by a line break and
* closed by three double quotes again.
*/
@Test
public void textBlocksTest() {
String textBlockFootballers = """
Footballers
with double space indentation
and "Team Nitin" Rocks!
""";
System.out.println(textBlockFootballers);
}
/**
* We can make the same text one-liner with the "\" character. Let's see the example below.
*/
@Test
public void textBlocksNoLineBreaksTest() {
String textBlockFootballers = """
Footballers \
with double space indentation \
and "Team Nitin" Rocks! \
""";
System.out.println(textBlockFootballers);
}
/**
* We can insert variables into a text block by using the static method String::format or with the String::formatted.
*/
@Test
public void textBlocksInsertingVariablesTest() {
String textBlockFootballers = """
Footballers
with double space indentation
and "%s" Rocks!
""".formatted("Team Nitin");
System.out.println(textBlockFootballers);
}
  • They are immutable.
  • Their fields are private and final.
  • Record Class defines canonical constructors for all fields.
  • All fields have Getters, equals, hashCode, and toString.
record Student(String name, int age, String class) { }
  • Private final fields for age, name, and class.
  • Canonical constructors for all fields.
  • Getters for all fields.
  • equals, hashCode, and toString for all fields.
  • We can define additional methods.
  • Implement interfaces.
  • Customize the canonical constructor and accessors.
/**
* Records reduce boilerplate code for classes that are simple data carriers.
* They are immutable (since their fields are private and final).
* They are implicitly final.
* We cannot define additional instance fields.
* They always extend the Record class.
*/
public class Records {
@BeforeEach
void setup(TestInfo testInfo) {
System.out.println(testInfo.getDisplayName());
}

@AfterEach
void teardown() {
System.out.println();
}

/**
* With below record declaration, we automatically define:
* Private final fields for age, name, and class.
* Canonical constructors for all fields.
* Getters for all fields.
* equals, hashCode, and toString for all fields.
*/
record Student(String name, int age, String class) { }

//Canonical Constructor
Student student = new Student("Nitin", 36, "MCA");

@Test
public void recordTest() {
//Getters without get prefix
System.out.println("Student name: " + student.name);
System.out.println("Student age: " + student.age);

record Teacher(String name, int age) { }

// equals
boolean isStudent1 = student.equals(new Student("Ishaan", 2, "PS")); // false
System.out.println("Is first one student? " + isStudent1);

boolean isStudent2 = student.equals(new Student("Nitin", 36, "MCA")); // true
System.out.println("Is second one student? " + isStudent2);

//hashcode
int hashCode = student.hashCode();
System.out.println("Hash Code of Record: " + hashCode);

//toString
String toStringOfRecord = student.toString();
System.out.println("ToString of Record: " + toStringOfRecord);
}

/**
* We can define additional methods.
* Implement interfaces.
* Customize the canonical constructor and accessors.
*/
@Test
public void record2Test() {
record Engineer(String name, int age) {
//Explicit canonical constructor
Engineer {
//Custom validation
if (age < 1)
throw new IllegalArgumentException("Age less than 1 is not allowed!");
//Custom modifications
name = name.toUpperCase();
}

//Explicit accessor
public int age() {
return this.age;
}
}

Engineer engineer1 = new Engineer("Anil", 37);
System.out.println(engineer1);
Assertions.assertEquals("ANIL", engineer1.name);

Exception exception = Assertions.assertThrows(IllegalArgumentException.class, () -> new Engineer("Milo", 0));
Assertions.assertEquals("Age less than 1 is not allowed!", exception.getMessage());
}

}
/**
* Sealed Parent Class which only allows Square and Rectangle as its children.
*/
@Getter
public sealed class Shape permits Square, Rectangle{
protected int edge1, edge2;

protected Shape(int edge1, int edge2) {
this.edge1 = edge1;
this.edge2 = edge2;
}
}
/**
* Sealed Interface
*/
public sealed interface ShapeService permits Square, Rectangle {
default int getArea(int a, int b) {
return a * b;
}

int getPerimeter();
}
public final class Rectangle extends Shape implements ShapeService {

public Rectangle(int edge1, int edge2) {
super(edge1, edge2);
}

@Override
public int getPerimeter() {
return 2 * (edge1 + edge2);
}
}
public final class Square extends Shape implements ShapeService {
public Square(int edge1, int edge2) {
super(edge1, edge2);
}

@Override
public int getPerimeter() {
return 4 * edge1;
}
}
//public final class Triangle extends Shape implements ShapeService {} -> Triangle is not allowed in the sealed hierarchy!!!
public final class Triangle {
private final int base;
private final int edge1;
private final int edge2;
private final int height;

public Triangle(int base, int edge1, int edge2, int height) {
this.base = base;
this.edge1 = edge1;
this.edge2 = edge2;
this.height = height;
}

public int getPerimeter() {
return base + edge1 + edge2;
}

public int getArea() {
return (base * height) / 2;
}
}
public class LocalVariableTypeInference {public static void main(String[] args) throws IOException {
List<Set<Integer>> listOfGenerics = List.of(Set.of(1, 2), Set.of(2, 3), Set.of());
try (var bw = Files.newBufferedWriter(Path.of("test.txt"))) {
for (var set : listOfGenerics) {
bw.write(set.toString());
}
}
}
}

Conclusions

If you compare latest Java with Java 8, the modern Java code has more possibilities to be less verbose. If we start Java project from scratch we could somewhat benefit from the syntax of the modern Java.

  • Because you gain something
  • Because you have nothing more important to do

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
nitin gupta

nitin gupta

Senior Technical manager with 12+ years of experience in Java, springboot, database