Included in this document are some of the updates and features that have been made in Java from Java 8 through the most current version of Java. This document has been designed to help educators and organizations that educate students at the high school and college level to become aware of new features that should be considered as part of an introduction to Java experience. The benefits of helping educators and organizations get current and stay current come in the form of keeping pace with the evolution of Java, ensuring students are being taught the expected industry standard for how code should be written, and to increase engagement of programming students using Java.
Updates to the core Java language are being released every 6 months, in September and March.
The features included in this brief are organized by release and separated into three categories: Features of Data Oriented Programming, Features to Increase Engagement, and Features to Support Instruction. For each feature, we have included a link to the corresponding JEP page, description of the feature, and some examples for how to use the feature.
This article provides an overview of features that are recommended for intro to CS courses to include.
The features listed in this section are recommended for inclusion in introductory computer science courses and data structures courses (i.e., CS 1 or CS 2 courses) that include a focus on data-oriented programming. These features reflect current, more modern ways of introducing programming using Java, beyond just object-oriented programming.
Features:
Originally previewed in JEP 447 with a second preview in JEP 482.
Constructors will be allowed to include statements that do not reference the instance being created prior to an explicit constructor call such as super(…)
and this(…)
.
An example of a good use of this feature would be as a check on parameters being passed in prior to using them.
Uses in Intro to CS
Recommended to be included once finalized.
Learn more: JEP 492: Flexible Constructor Bodies (Third Preview)
When variable declarations or nested patterns are required but not used, an underscore (' _
') can be used instead.
Some examples:
try {
//something
} catch (Exception ex) {
//error message
}
The variable ex
isn’t being used, so we could re-write as:
try {
//something
} catch (Exception _ ) {
//error message
}
Another example for when you want to grab a specific value in a record, but do not need the other data:
public class DogTester {
public static void main(String [] args) {
Dog d = new Dog(new LicName("Brady the Blue Brindle", "Brady"), "Whippet", 46.7);
Cat c = new Cat("Sweetie", "Siamese", 7.5);
ArrayList <Animal> animals = new ArrayList<Animal>();
animals.add(d);
animals.add(c);
for (Animal a: animals) {
if (a instanceof Dog(LicName(_,String nickname), _, _)) {
System.out.println(nickName);
}
if (a instanceof Cat(String n, _, _)) {
System.out.println(n);
}
}
}
}
sealed interface Animal permits Dog, Cat {
}
record Dog(LicName name, String Breed, double weight) implements Animal {
}
record Cat(String name, String Breed, double weight) implements Animal {
}
record LicName(String fullName, String nickname) {
}
Uses in Intro to CS
This would be recommended for any courses that include data in their course. Data science courses would benefit from the use of records and record patterns. Incorporating real-world data has the potential to increase engagement for students.
Learn more: JEP 456: Unnamed Variables & Patterns
Resources
This feature has considerable interaction with Record Patterns JEP 440: Record Patterns, which is covered above.
Similar to what we saw in Record Patterns, the Pattern Matching for Switch includes:
- a type check in the form of case checks;
- creation and assignment of variable; and
- typecasting of the variable.
sealed interface Animal permits Dog, Cat {
}
ArrayList<Animal> animList = new ArrayList<>();
for (Animal a : animList) {
switch (a) {
//uses pattern matching to declare dog and assign it to (Dog)a
case Dog dog: {
System.out.println(dog.name().showName());
break;
}
case Cat cat: {
System.out.println(cat.name());
break;
}
}
}
Or combine with Unnamed Patterns and Variables (JEP 456: Unnamed Variables & Patterns) and rule switching with Lambda notation.
for (Animal a : animList) {
switch (a) {
//uses rule switch with lambda
case Dog(LicenseName(_, String nickname), _, _) ->
System.out.println(nickName);
case Cat(String name, _, _) -> System.out.println(name);
}
}
Since we are using a sealed interfaces that only allows implementation with Dog
and Cat
, a default statement is not necessary.
Sealed Classes were finalized in the JDK 17 (JEP 409: Sealed Classes).
Uses in Intro to CS
This would be recommended for any courses that include data in their course. Data science courses would benefit from the use of records and record patterns. Incorporating real-world data has the potential to increase engagement for students.
Learn more: JEP 441: Pattern Matching for switch
Resources
This is an extension of JEP 394: Pattern Matching for instanceof, which allowed the instanceof
operator to take a type pattern and perform pattern matching.
A pattern consists of a test and a set of local variables, known as pattern variables.
A type pattern consists of a test for the type of variable and a pattern variable.
sealed interface Animal permits Dog, Cat {
}
ArrayList<Animal> animList = new ArrayList<>();
for (Animal a: animList) {
//Old way
if (a instanceof Dog){
Dog dog = (Dog) a;
System.out.println("Dog: " + dog);
}
//New way
if (a instanceof Dog dog) {
System.out.println("Dog: " + dog);
}
In the new way, a instanceof Dog dog
completes the following:
- the type check;
- the creation and assignment of variable
dog
; and
- casts
a
to type Dog
.
Rather than creating a new Dog dog
, we can create variables for the instances variables of Dog
we are actually going to use, as seen below.
if (a instanceof Dog(LicenseName (String showName, String nickName), String breed, double wt)) {
System.out.println(showName + " is a " + breed);
}
NOTE: The Animal
interface is a sealed interface. Sealed Classes were finalized in the JDK 17 (JEP 409: Sealed Classes).
Uses in Intro to CS
This would be recommended for any courses that include data in their course. Data science courses would benefit from the use of records and record patterns. Incorporating real-world data has the potential to increase engagement for students.
Learn more: JEP 440: Record Patterns
Resources
Sealed classes and interfaces restrict which other classes or interfaces can extend or implement them. This allows the originator to control which code has the responsibility for implementing it.
Using sealed classes and interfaces, support pattern matching for switch (JEP 441: Pattern Matching for switch) made final in the JDK 21 by allowing switch cases to be exhaustive without the need for a default clause.
For example, the Animal
interface below allows for only Dog
and Cat
to implement it:
sealed interface Animal permits Dog, Cat {
}
Uses in Intro to CS
This would be recommended for any courses that include data in their course. Data science courses would benefit from the use of records and record patterns. Incorporating real-world data has the potential to increase engagement for students.
Learn more: JEP 409: Sealed Classes
In data-oriented programming, records allow us to treat data as non-modifiable, which is generally not the case for objects. Records are simply the right tool for dealing with non-modifiable data.
Records are simple to create and are meant to be used with non-modifiable data. The way in which they are designed protects the data from being altered making it the safer and right solution.
Consider a common CS 1 Project to write a Point
class:
public class Point {
private int x;
private int y;
public Point() {
x = 0;
y = 0;
}
public Point(int myX, int myY) {
x = myX;
y = myY;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
This class can be rewritten using a record as follows:
record PointRecord(int x, int y) {
}
The record supplies -
Below is an example of how to use the Point
class and PointRecord
record:
public static void main(String[] args) {
Point p = new Point();
System.out.println("(" + p.getX() + ", " + p.getY() + ")");
Point p2 = new Point(5, 7);
System.out.println("(" + p2.getX() + ", " + p2.getY() + ")");
PointRecord p3 = new PointRecord(-3, 4);
System.out.println("(" + p3.x() + ", " + p3.y() + ")");
}
Records also include the following automatically:
- toString()</li>
- hashCode()</li>
- equals()</li>
toString()
Example:
public static void main(String [] args) {
Point p = new Point();
System.out.println("(" + p.getX() + ", " + p.getY() + ")");
Point p2 = new Point(5, 7);
System.out.println("(" + p2.getX() + ", " + p2.getY() + ")");
PointRecord p3 = new PointRecord(-3, 4);
System.out.println("(" + p3.x() + ", " + p3.y() + ")");
System.out.println(p);
System.out.println(p2);
System.out.println(p3);
}
Output:
(0, 0)
(5, 7)
(-3, 4)
Point@7bb11784
Point@33a10788
PointRecord[x=-3, y=4]
The first two are Point
objects and we are getting a number generated by the Java Virtual Machine:
Point@7bb11784
Point@33a10788
The third one is a PointRecord
object that contains a toString()
method and prints:
PointRecord[x=-3, y=4]
equals()
Example:
public static void main(String[] args) {
Point p = new Point();
Point p2 = new Point(5, 7);
PointRecord p3 = new PointRecord(-3, 4);
PointRecord p4 = new PointRecord(0, 0);
PointRecord p5 = new PointRecord(-3, 4);
System.out.println(p3.equals(p4));
System.out.println(p3.equals(p5));
System.out.println(p3 == p5);
}
Output:
false
true
false
The equals()
method will examine the instance variables of each object to see if they are equal. Since the x
value of p3
is –3
and the x
value of p4
is 0
AND the y
value of p3
is 4
and the y
value of p4
is 0
, these two objects are not equal in value and the method returns false
.
Since the x
value of p3
and p5
are both –3
AND the y
value of p3
and p5
are 4
, these two objects are equal in value and the method returns true
.
While p3
and p5
are equal in value they are not the same object. Each object is distinct and stored separately in memory. Therefore, the method returns false
.
Additional methods can be included in Records as well.
Uses in Intro to CS
This would be recommended for any courses that include data in their course. Data science courses would benefit from the use of records and record patterns. Incorporating real-world data has the potential to increase engagement for students.
Learn more: JEP 395: Records
Resources
Extends the switch semantics. Now a switch
can be an expression.
For example:
int grade = 9;
String gradeText = switch(grade) {
case 9, 10: {
yield "Under-classman";
}
case 11, 12: {
yield "Upper-classman";
}
default: {
yield "Other";
}
};
For switch expressions, yield is used to return the value of the expression. There is no fall through in switch expressions.
int month = 5;
String myMonth = switch(month) {
case 1 -> "January";
case 2 -> "February";
case 3 -> "March";
case 4 -> "April";
case 5 -> "May";
case 6 -> "June";
case 7 -> "July";
case 8 -> "August";
case 9 -> "September";
case 10 -> "October";
case 11 -> "November";
case 12 -> "December";
default -> "That is not a month";
};
System.out.println (myMonth);
In this case, the lambda-style arrow notation ( ->
) specifies the return the value of the expression. As it is an expression, there is no fall through.
Uses in Intro to CS
This would be recommended for any courses that include data in their course. Data science courses would benefit from the use of records and record patterns. Incorporating real-world data has the potential to increase engagement for students.
Learn more: JEP 361: Switch Expressions
Resources
In cases where the type of the variable can be inferred, the keyword var
can be used instead of the type. These cases include local variables being declared with initializers, enhanced for-loops indexes, and index variables declared in traditional for loops. You can also use var
for arguments in lambda expressions and for pattern variables in record patterns.
Some examples of variable declarations are as follows:
var brady = new Dog();
var wordlist = new ArrayList<String> ();
An example of an enhanced for-loop is as follows:
for (var word : wordlist) { ...
Uses in Intro to CS
The use of var
could be incorporated in the earliest introductory computer science course.
Some examples of improper uses of var
are as follows:
Without an initialization, the compiler is unable to infer the type of wordlist
.
Since null
can be assigned to objects of any type, the compiler is unable to infer the type of brady.
Learn more: JEP 286: Local-Variable Type Inference
Resources
The following features might be useful in creating more engaging lab experiences for students and foster an increased engagement.
Originally previewed in JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview).
This preview features is an enhancement to pattern matching to extend the instanceof (JEP 394: Pattern Matching for instanceof) and switch (JEP 441: Pattern Matching for switch) to work with primitive variables.
Previously, you could only use instanceof
on reference types, to answer the question of whether an object was of a specific type. With primitive patterns you can use instanceof with primitive types, to answer the question of whether the variable can be represented by a specific type without any loss of precision or data.
For example, we can say:
int x = 100;
if (x instanceof byte b) {
System.out.println (b + " is in the byte range");
else{
System.out.println (b + " is not in the byte range");
}
We can now switch over all primitive types. If we are using a switch expression, then we will need to be sure that all cases are being covered. We do this by including cases with additional guards. For example, we can say:
int x = //…
String result = switch (x){
case 0 -> "zero";
case int y when y < 0 -> "negative";
case int _ -> "positive";
}
Some important things to note here. The reserved word when
is being used as a condition for the second case statement. We cannot use when
again in the third case as it would cause the compiler to detect that the switch statement is not exhaustive. We use the last case statement as an option for all cases that aren’t the first two cases. This makes the switch exhaustive as required.
Also, note that since we are not using the variable in the third case, we can simply use an unnamed variable (_
) as a placeholder. Please see JEP 456: Unnamed Variables & Patterns for more information on unnamed variables.
We can also use switch expression instead of the ternary conditional operator (?:
). We can also use a switch to allow for statements as well as expressions.
For example, we could use the ternary conditional operator:
String result = (x % 2 == 0) ? "Even" : "Odd";
Which is equivalent to this if..else
statement:
if (x % 2 == 0) {
result = "Even";
} else {
result = "Odd";
}
And finally using a switch expression:
String result = switch(x % 2 == 0) {
case true -> "Even";
case false -> "Odd";
};
If we would rather report out on this information, we could write the following:
switch(x % 2 == 0) {
case true -> System.out.println ("Even");
case false -> System.out.println ("Odd");
}
In this case the ternary conditional operator would not allow for a statement, since it uses expressions only. We could use an if..else
statement here as well.
Uses in Intro to CS
Switch statements and switch expressions in general may help increase students understanding of conditionals and in some cases are better than using a long series of if..else
statements. Students who learn ternary conditional operators, may choose to write their solutions using switch expressions instead. This feature has the potential to increase engagement for students.
Learn more: JEP 488: Primitive Types in Patterns, instanceof, and switch (Second Preview)
Resources
In addition to the Collection
class, the ArrayList
class now also implements the SequencedCollection
. The SequencedCollection
, contains the following new methods which would be considered legal Java during the AP Computer Science A exam:
addFirst(E e)
– adds an element as the first element of this collection
addLast(E e)
– adds an element as the last element of this collection
getFirst()
– Gets the first element of this collection
getLast()
– Gets the last element of this list
removeFirst()
– Removes and returns the first element of this collection
removeLast()
– Removes and returns the last element of this collection
reversed()
– Returns a reverse-order view of this collection
Uses in Intro to CS
This new content could impact course work where students are asked to access or move specific elements or to add element to an ArrayList by adding each element to the front of the list. This feature could have an impact on specific sorting algorithms.
Learn more: JEP 431: Sequenced Collections
Resources
A pattern consists of a test and a set of local variables, known as pattern variables.
A type pattern consists of a test for the type of variable and a pattern variable.
sealed interface Animal permits Dog, Cat {
}
ArrayList<Animal> animList = new ArrayList<>();
for (Animal a: animList) {
//Old way
if (a instanceof Dog) {
Dog dog = (Dog)a;
//do something with dog
}
//New way
if (a instanceof Dog dog) {
//do something with dog
}
}
Uses in Intro to CS
This would be recommended for any courses that include data in their course. Data science courses would benefit from the use of records and record patterns. Incorporating real-world data has the potential to increase engagement for students.
Learn more: JEP 394: Pattern Matching for instanceof
Resources
A text block is a multi-line string literal that avoids the needs for most escape sequences, automatically formats the string in a predictable way, and gives the developer control over the format when desired.
The opening delimiter is a sequence of three double quote characters ("""
) followed by zero or more white spaces followed by a line terminator.
The content begins at the first character after the line terminator of the opening delimiter and ends at the last character before the first double quote for the closing delimiter.
The closing delimiter is a sequence of three double quote characters ("""
).
Some examples:
System.out.println("first\nsecond\nthird\n");
Prints the same result as:
System.out.println("""
first
second
third
""");
While a line terminator is required after the opening delimiter, it is not required before a closing delimiter.
The following will cause an error since there is no line terminator before the word first.
System.out.println("""first
second
third
""");
However, the line terminator after third is not required and the following will produce the same result as the original example.
System.out.println("""
first
second
third""");
Uses in Intro to CS
Could impact how students respond to coursework and questions that require them to create strings using a particular spacing.
Learn more: JEP 378: Text Blocks
Relates to JEP 286: Local-Variable Type Inference.
This feature allows the use of var
when establishing the parameters in lambda expressions.
For example, consider the following Computations
interface:
public interface Computations {
public double operation(double first, double second);
}
We can define the functionality of operation
as follows:
Computations subt = (f, s) -> f - s;
System.out.println(subt.operation(3, 2));
We can also define the functionality of operation using var
as follows:
Computations subt = (var f, var s) -> f - s;
System.out.println(subt.operation(3, 2));
This aligns the syntax of the formal parameters in lambda expressions with the syntax of a local variable declaration.
Uses in Intro to CS
For courses that are currently incorporating lambda, this feature should be included in the course of study.
Learn more: JEP 323: Local-Variable Syntax for Lambda Parameters
This feature allows for small non-modifiable sets of data to be set to Lists, Maps, and Sets, using the of method.
For example, instead of adding the following names to a student roster by calling the add
method repeated, such as:
List<String> roster = new ArrayList<>();
roster.add("Mark");
roster.add("Liam");
roster.add("Aidan");
roster.add("Emelia");
If the roster is non-modifiable, meaning we won’t need to add, remove, or change any of the elements, we can create the list using the of
method. This is similar to using an initializer list with arrays. For example:
List<String> names = List.of("Mark", "Liam", "Aidan", "Emelia");
Note that you cannot add any null values to list created with the List.of()
factory method.
Uses in Intro to CS
This would be recommended for any courses that include data in their course. Data science courses would benefit from the use of records and record patterns. Incorporating real-world data has the potential to increase engagement for students.
Learn more: JEP 269: Convenience Factory Methods for Collections
Full details on Project Lambda can be found here: OpenJDK: Project Lambda
Lambda’s allow programmers to implement an interface that contains one method, without having to write the full class. They consist of a parameter list, the arrow token, and a return value based on the body of the code statements.
For example, consider the following Computations
interface:
public interface Computations {
public double operation(double first, double second);
}
We could create classes AdditionComputation
and MultiplyComputation
as well as several others, such as:
public class AdditionComputation implements Computations {
@Override
public double operation(double first, double second) {
return first + second;
}
}
public class MultiplyComputation implements Computations {
@Override
public double operation(double first, double second) {
return first * second;
}
}
To use these classes, we might write the following:
public class LambdaTester {
public static void main(String[] args) {
AdditionComputation add = new AdditionComputation();
MultiplyComputation mult = new MultiplyComputation();
System.out.println(add.operation(5.3, 2.3));
System.out.println(mult.operation(5, 3));
}
}
This would mean that every time we wanted to provide unique functionality for the operation
method in Computations
and use it, we would need to first create a class that implements Computations
, then create an instance of this class, and finally call operation
.
If we use lambda, we can create an instance of Computations
that can be used to call the operation
method and provide the functionality as part of declaring and creating that instance.
For example:
Computations subt = (f, s) -> f - s;
System.out.println(subt.operation(3,2));
Uses in Intro to CS
The use of lambda would be appropriate in a course where interfaces are being used. Additionally, it could easily be incorporated into any course that uses data and ArrayList
. Some examples of the ArrayList forEach
method are as follows.
The forEach
method of the Collection
interface takes a Consumer
object as a parameter. Consumer
is an interface that has an accept
method that needs to be implemented. The accept
method performs an operation on the given argument.
The following examples pass the implementation of the method accept
as an argument to the forEach
method. The accept
method is void
, so we have included the output statement as part of the implementation.
The following prints out all of the values of the list:
names.forEach((n) -> System.out.println(n));`
The following prints out all of the values that have a length greater than 4:
names.forEach((n) -> {
if (n.length() > 4) {
System.out.println(n);
}
});
The following prints out the values that start with the letter "A":
names.forEach((n) -> {
String first = n.substring(0,1);
if (first.equals ("A")) {
System.out.println(n);
}
});
The implementation of a lambda expression can use variable defined outside of itself, as long as they are final, or at least not modified. In that case, the compiler can make this variable final for you. These non-final variable that can be made final by the compiler are called effectively final variables. The following code wouldn’t work because it requires the use of a local variable, and tries to modify it. So this local variable is not final, and cannot be made final.
int sum = 0;
names.forEach((n) -> {
sum += n.length();
});
System.out.println(sum/names.size());
Learn more: JEP 126: Lambda Expressions & Virtual Extension Methods
Resources
The following features could impact the way the course is taught. Many of these features make introducing new topics easier for students. This section will also include deprecated APIs that will no longer be supported in the future, yet we know that some teachers might still be using these materials.
Features:
Originally previewed in JEP 445: Unnamed Classes and Instance Main Methods (Preview), second preview in JEP 463: Implicitly Declared Classes and Instance Main Methods (Second Preview) and third preview in JEP 477: Implicitly Declared Classes and Instance Main Methods (Third Preview).
Great for beginners just learning Java, Simple Source Files and Instance Main Methods allows new and experienced programmers to write smaller programs succinctly, without the need for constructs intended for programming in the large. This is great for beginners and those who want to rapidly prototype or try code snippets.
This preview feature automatically imports three static methods for simple text input and output with the console. These methods are declared in the new top-level class java.io.IO
.
Instead of:
public class MyClass {
public static void main(String [] args) {
System.out.println ("Java Rocks!");
}
}
We can write the following:
void main() {
println ("Java Rocks!");
}
It also automatically imports the top-level classes in the java.base
module. This includes java.util
which will allow for the use of ArrayList
without import statements. Other useful classes that are included and could be incorporated in an introductory CS course to raise engagement are: java.time
, java.text
, java.io
, and java.math
.
Uses in Intro to CS
This content is shared as an instructional tool to help get students into the core Java content more quickly. It is similar to products that include an interactions pane with the added benefit of the code being in a .java
file so it can be saved, tweaked, and run multiple times without having to re-type.
Another resource that allows teachers to learn about and demonstrate code snippets is the Java Playground.
NOTE: this is not an online IDE and doesn’t allow for saving program code.
Learn more: JEP 495: Simple Source Files and Instance Main Methods (Fourth Preview)
In addition to being able to write documentation comments in JavaDoc and HTML, you can now use Markdown. Markdown is typically much easier to write and read within the code and translates to HTML.
Uses in Intro to CS
Comments written in markdown are easier to read and write within the code than HTML. If you would like to include more formatting in comments for starter code, using markdown would be the right tool to use.
Learn more: JEP 467: Markdown Documentation Comments
Expands prior JEP 330: Launch Single-File Source-Code Programs
by allowing multiple files to be compiled and run at the same time, rather than needing to compile the files first and then run them.
Uses in Intro to CS
Multi-file and single-file source-code launcher provides instructors and students with more options in terms of tools that can be used and how they approach introducing concepts to students.
Single-file and multi-file source-code works well in jShell or IDEs that leverage jShell.
Learn more: JEP 458: Launch Multi-File Source-Code Programs
A helpful upgrade to NullPointerException
errors. At the point in a program where code tries to access a null reference, an exception message will be displayed that names which variable was null
.
Uses in Intro to CS
A helpful instructional tool that gives a little more information and can help identify the source of the exception better.
Learn more: JEP 358: Helpful NullPointerExceptions
This new feature allows multiple classes to be saved in the same .java
file.
For example, saved in the DogTester.java
file:
class Dog {
private String name;
private String breed;
private double weight;
public Dog(String n, String b, double w) {
// body
}
}
public class DogTester {
public static void main(String[] args) {
Dog d = new Dog("Brady", "Whippet", 42);
...
}
}
The name of the file should be the same as the class that contains the main
method.
You can add multiple classes or records. For example:
public class DogTester {
public static void main(String [] args) {
Dog d = new Dog(new LicName("Brady the Blue Brindle",
"Brady"), "Whippet", 46.7);
System.out.println(d);
}
}
record Dog(LicName name, String Breed, double weight) {
}
record LicName(String fullName, String nickname) {
}
Uses in Intro to CS
Single-file source-code launcher provides instructors with more options in terms of tools that can be used and how they approach introducing concepts to students.
Single-file source-code works well in jShell, IDEs that leverage jShell, and the Java Playground.
Learn more: JEP 330: Launch Single-File Source-Code Programs
JShell allows for rapid prototyping of lines of code, or snippets.
Requires the use of the command prompt to run the program code.
Uses in Intro to CS
This feature could be used in the earliest introductory computer science course as a way to introduce new topics and quickly demonstrate the functionality associated with particular snippets of code.
Learn more: JEP 222: jshell: The Java Shell (Read-Eval-Print Loop)