In this article, we will learn all about lambda functions in Java and Custom Functional Interfaces with examples. It is good to learn lambda declarations with custom functional interfaces together. Let’s create a custom interface as PlayMusicService which has a single abstract run method.
//SAM: Single Abstract Method (Functional Interface annotation dictates that this interface has single abstract method.) @FunctionalInterface public interface PlayMusicService { void run(String song); }
Now, let’s implement this run method in a test class in two ways, the classical way with @override annotation and the more modern way with a lambda function declaration.
public class PlayMusicServiceTest { @Test public void playMusicWithoutLambdaTest(){ PlayMusicService playIt = new PlayMusicService() { @Override public void run(String song) { System.out.println("First playing: "+ song); } }; playIt.run("Despacito"); } @Test public void playMusicWithLambdaTest(){ PlayMusicService playIt = (song) -> System.out.println("First playing: "+ song); //lambda declaration. playIt.run("Despacito"); } }
As you can see, with starting Java 8, lambda declarations make our programming life better and neater. We can implement the same functionality with less code. Lambdas simplify the long @override way of implementation in this way.
new PlayMusicService() {
@Override public void run(String song) {
is transformed as
(song) ->
and inside of the run method is the same for both implementations.
System.out.println(“First playing: “+ song);
Let’s run our test and see the result.
Lamda Functions as a Parameter in Java
One of the powerful features of functional programming is we can define lambda functions as parameters and send them to the functions as arguments. I want to show this with an example. Let’s assume we have PlayService and it has a run method that we can implement.
//SAM: Single Abstract Method (Functional Interface annotation dictates that this interface has single abstract method.) @FunctionalInterface public interface PlayService { void run(String name, double duration, boolean repeat); }
Now, we will create a test class and in this class, we have testPlay() method. In this method, we will send a lambda function as an argument.
public class PlayTest { public static void main(String[] args) { //Function definition PlayService playIt = (name, duration, repeat) -> System.out.println(name.toUpperCase() + " is playing for " + duration + " minutes." + " Repeat is: " + repeat + "\n"); //Sending the lambda function as an argument. testPlay(playIt); //Function calling by sending the function as an argument as lambda declaration. testPlay((name, duration, repeat) -> System.out.println(name.toLowerCase() + " is playing for " + duration + " minutes." + " Repeat is: " + repeat + "\n")); } //Declaring lambda function as a parameter. private static void testPlay(PlayService playService) { System.out.println("This is a song play test!"); playService.run("Despacito", 3.20, true); System.out.println("This is a video play test!"); playService.run("Messi Skills", 4.20, true); } }
As you can see above code, our testPlay(PlayService playService) method has a parameter as lambda function and in the main class we declare this lambda function as:
//Function definition PlayService playIt = (name, duration, repeat) -> System.out.println(name.toUpperCase() + " is playing for " + duration + " minutes." + " Repeat is: " + repeat + "\n");
and call the testPlay() method by sending the playIt lambda function as an argument.
//Function calling by sending the function as an argument. testPlay(playIt);
We can even directly use the lambda declaration inside the testPlay method’s parameter section.
//Function calling by sending the function as an argument as lambda declaration. testPlay((name, duration, repeat) -> System.out.println(name.toLowerCase() + " is playing for " + duration + " minutes." + " Repeat is: " + repeat + "\n"));
Let’s run the test and see the results.
Here, we call testPlay() method two times, for the first one we send a lambda function that has an upper-case functionality, and the second one has a lower-case functionality.
Let’s do one more example. Assume that we have a shopping cart and we have a CartAction interface with the apply method.
@FunctionalInterface public interface CartAction { void apply(String message, boolean isExist); }
We will implement this apply method with several lambda functions like addItem, deleteItem, deleteCart. Also, we have an action method that takes the CartAction lambda function as a parameter. Finally, we will write two tests one will be an example of sending lambda functions as an argument and the other one is directly use the lambda functions. Here is the code.
public class ShoppingCartTest { int itemCount = 0; CartAction addItem = (message, status) -> { itemCount++; System.out.println(message + " | Item count in the cart is: " + itemCount + " | Cart existence: " + status); }; CartAction deleteItem = (message, status) -> { itemCount--; System.out.println(message + " | Item count in the cart is: " + itemCount + " | Cart existence: " + status); }; CartAction deleteCart = (message, status) -> { itemCount = 0; System.out.println(message + " | Item count in the cart is: " + itemCount + " | Cart existence: " + status); }; private void action(CartAction action, String message, boolean status) { action.apply(message, status); } //Sending lambda functions as an argument version @Test public void shoppingCartTest1() { System.out.println("Shopping Cart Test 1! Function as parameter!"); action(addItem, "Item has been added!", true); action(addItem, "Item has been added!", true); action(addItem, "Item has been added!", true); action(deleteItem, "Item has been deleted!", true); action(deleteItem, "Item has been deleted!", true); action(deleteCart, "Cart has been deleted!", false); action(addItem, "Item has been added!", true); action(deleteItem, "Item has been deleted!", true); action(addItem, "Item has been added!", true); action(addItem, "Item has been added!", true); action(deleteCart, "Cart has been deleted!", false); System.out.println("--------------------------------------------"); } //Directly calling lambda functions @Test public void shoppingCartTest2() { System.out.println("Shopping Cart Test 2! Direct lambda function call!"); addItem.apply("Item has been added!", true); addItem.apply( "Item has been added!", true); addItem.apply( "Item has been added!", true); deleteItem.apply("Item has been deleted!", true); deleteItem.apply("Item has been deleted!", true); deleteCart.apply("Cart has been deleted!", false); addItem.apply("Item has been added!", true); deleteItem.apply("Item has been deleted!", true); addItem.apply("Item has been added!", true); addItem.apply("Item has been added!", true); deleteCart.apply("Cart has been deleted!", false); System.out.println("--------------------------------------------"); }
The output of the test is shown below.
Lambda Functions with Method References
We can declare the lambda functions with method references. For this we use a double colon ::
Let’s see this with an example!
public class MethodReferencesExample { @Test public void lambdaWithoutMethodReference(){ //Function definition with lambda without method reference PrintService printUpperCase = text -> text.toUpperCase(); //Function call String upperCaseText = printUpperCase.apply("software test academy"); //Print the result System.out.println(upperCaseText); } @Test public void lambdaWithMethodReference(){ //Function definition with lambda with method reference PrintService printUpperCase = String::toUpperCase; //Function call String upperCaseText = printUpperCase.apply("software test academy"); //Print the result System.out.println(upperCaseText); } }
GitHub Project
https://github.com/swtestacademy/java-functional/tree/main/src/test/java/functional/lambda
Thanks for reading,
Onur

Onur Baskirt is a Software Engineering Leader with international experience in world-class companies. Now, he is a Software Engineering Lead at Emirates Airlines in Dubai.