
A Look at New Java Features in Test Automation
Since Java 8, there have been many cool features introduced. In fact, Java is releasing a new version every 6 months! With so many additions, it can be hard to keep up.
Many of the features were added in an attempt to improve Java’s reputation of being verbose by eliminating a lot of the boilerplate code needed to do the simplest of things.
Here’s how some of the newer features from Java 8 – 12 can be used in test automation. I’ll demonstrate these features by automating a scenario for the Todo application.

Creating collections
In Java 9, a new static factory method called of() was added to Collections framework classes such as List, Set, and Map. This method makes it so much easier to create new lists.
Prior to Java 9, if we wanted to create a list of items to add as tasks, we could do so like this:
1 |
List<String> tasks = Arrays.asList("pay mortgage bill", "make dentist appointment", "get car washed"); |
This isn’t terrible. In fact, it’s an improvement over creating the List object and then calling add() method for each element we wanted to add to the list.
However, in Java 9 we can use List.of() which reads better, requires one less import, and is a lot easier to remember:
1 |
List<String> tasks = List.of("pay mortgage bill", "make dentist appointment", "get car washed"); |
One downfall to creating collections using the of() method is that the collection is immutable, meaning you cannot change it by adding to it later, removing elements, sorting it, etc.
Type Inference for Local Variables
One of the things that makes Java so verbose is the requirement to explicitly declare the type of any variable or object used. This becomes especially tedious when the type includes the diamond operator like in our list above, or when the object’s type has a long name like for our page object classes.
Java 10 introduces type inference for local variables, which allows us to declare them using the reserved word var. When using var, you must initialize the variable at the time of declaration. This is because Java is going to infer the type based on the assignment.
For example, as opposed to specifying our tasks variable as List<String>, we can declare it using var, and since we have assigned it to a List.of() call using Strings, Java knows that tasks is a List<String>.
1 |
var tasks = List.of( "pay mortgage bill", "make dentist appointment", "get car washed"); |
I love using var; in fact, it’s my favorite new feature of Java. It makes coding in Java a lot less tedious! I do have to remind myself not to get carried away though, as readability is still very important. If I, the programmer, can’t tell what the type will be based on the right-hand side of the assignment, then it may be better to use the explicit data type or name my variable something that makes it very clear.
Functional programming features
Java 8 introduces lots of functional-style programming features such as lambda expressions. These can be used inside of the new forEach() method which has been added to classes in the Collections framework.
Before this Java 8 addition, we could use a traditional count-controlled for loop to add our tasks in the Todo app:
1 2 3 4 |
var tasks = List.of("pay mortgage bill", "make dentist appointment", "get car washed"); for(int i=0; i<tasks.size(); i++){ todosPage.addTask(tasks.get(i)); } |
Or we could use an enhanced for loop, which was less-verbose:
1 2 3 4 |
var tasks = List.of("pay mortgage bill", "make dentist appointment", "get car washed"); for(String task: tasks){ todosPage.addTask(task); } |
However, in Java 8, the forEach() method was added as a more concise, functional programming style to traverse over collections.
Using our list, we can do:
1 |
tasks.forEach( |
Then inside of the forEach() method, a lambda expression.
The expression begins with a name for the current element of the collection. We’ll call it task. Note, it’s ok to use a short, one-letter name for this variable because it’s final and local and cannot be used again outside of this method call. In real life, I would have named this t but for the sake of the tutorial, I gave it a full name.
1 |
tasks.forEach(task |
Then we provide an arrow and the action that should be performed on the element:
1 |
tasks.forEach(task -> todosPage.addTask(task)); |
In fact, since there is only one variable at play here and only one action that needs to be performed, we could shorten this expression even more by specifying it in the form of object::method and that way we don’t have to even worry with declaring a local variable for each element of the list. This means the same thing as the code snippet above but is more concise.
1 |
tasks.forEach(todosPage::addTask); |
However, if there were multiple actions that needed to take place for each item of the collection, we would simply use curly braces after the arrow:
1 2 3 4 |
tasks.forEach(task -> { todosPage.addTask(task); //TODO: more actions on task }); |
So, for our example, the final code would be:
1 2 |
var tasks = List.of("pay mortgage bill","make dentist appointment","get car washed"); tasks.forEach(todosPage::addTask); |
Streams
Now that we’ve added the tasks, we want to verify them. Let’s look at the method that will capture all of the tasks.
Prior to Java 8, we’d need to write code to find all of the WebElement objects for the tasks and then loop through them to get the text of each one. As we’re iterating, we’d need to add the Strings to a new list which we’d return:
1 2 3 4 5 6 7 8 9 10 11 |
public List<String> getAllTasks(){ List<WebElement> elementList = driver.findElements(tasksLocator); List<String> taskList = new ArrayList(); for(WebElement element : elementList){ taskList.add(element.getText()); } return taskList; } |
That’s quite a bit of code, and it’s very verbose! Fortunately, Java 8 introduced streams. Streams were added to allow us to process data of collections more easily.
We can start by calling the stream() method on the list of web elements returned from WebDriver’s findElements() method:
1 |
driver.findElements(tasksLocator).stream() |
From the stream, there are lots of methods to choose from, but in our case we want to use map() because we want to apply a function to the elements of this collection, namely, getting the text from the element [i.e., WebElement.getText()]:
1 |
driver.findElements(tasksLocator).stream().map(WebElement::getText) |
This returns the stream with the function applied, so this would now be all of the String values from each call to WebElement.getText().
We want to return this as a list of Strings so we call collect().
1 2 3 |
public List<String> getAllTasks(){ return driver.findElements(tasksLocator).stream().map(WebElement::getText).collect(Collectors.toList()); } |
As we can see this is a lot less code than the previous approach.
Switch Expressions
Java 12 introduces a new way to use the switch decision structure.
Let’s say that we wanted to make API calls within our code. Prior to Java 12, we would use a switch statement to determine which API method to invoke:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public static Response execute(String endpoint, HttpMethod method){ Response response = null; switch(method){ case GET: response = request.get(endpoint); break; case POST: response = request.post(endpoint); break; } response.then().log().all(); return response; } |
In each case, we’d need to do an assignment and also write a break statement.
Fortunately, with Java 12’s switch expressions, we can simplify this quite a bit. We can now directly assign a value to a variable based on the result of the switch processing.
In our case we will set response equal to the switch expression itself. Inside of this switch structure, we change the colon to an arrow and specify the value in this case. There’s no need for break or default statements:
1 2 3 4 5 6 7 8 9 10 |
public static Response execute(String endpoint, HttpMethod method){ Response response = switch(method){ case GET -> request.get(endpoint); case POST -> request.post(endpoint); } response.then().log().all(); return response; } |
Pingback: Five Blogs – 16 July 2019 – 5blogs
Vladimir Belorusets
Thank you, Angie. Very useful article.
David Misterek
Wow, so well explained. I’ll be using var from now on!
Swetha Reddy
Very useful article and informative would like to watch this blog daily to get more information on software testing
Steve
Thanks Angie! Very useful!
Mitul Vaghela
This is such a useful article Angie. I just keep coming back here! 🙂
Kumar Rohan
Thank you Angie. This is very helpful !