The ultimate guide to Functional interfaces in Java

We all have heard that Java Functional interfaces unleashed functional programming paradigm in Java.

That’s all cool but…what are these Functional interfaces?

Let’s try Oracle’s official documentation for the Functional interface:

Functional interfaces provide target types for lambda expressions and method references. Each functional interface has a single abstract method, called the functional method for that functional interface, to which the lambda expression’s parameter and return types are matched or adapted.


Hm. That did not help much? Or did that answer a bit?

Either way, don’t fret.

We will dive deep into Functional interfaces. Additionally, we will explore the most commonly used built-in interfaces like predicate, function, supplier and consumer.

This is a bit long article, if short on time, skip to the TLDR at the end from the Table of Contents. (There’s a summarised infographic šŸ˜ƒ)

Understanding Java’s Functional Interfaces

Flipchart, Presentation, Powerpoint, Office, Work, Draw

First question, what are Functional interfaces?

Simple, they are interfaces in Java that follow the following criteria:

  • Is an interface – duh! šŸ™†ā€ā™‚ļø
  • Has just one abstract method

The good thing to note here would be that, since Java 8, interfaces can have default methods So, having multiple or no default methods have no effect whatsoever on interface’s criteria for being a functional interface.

That’s clear, I hope.

Now onto @FunctionalInterface.

Remember the two rules we talked about Functional interfaces?

The @FunctionalInterface instructs the complier to treat and validate interface as functional interface. And hence, if compiler finds out that the criteria for functional interface is not met while the annotation @FunctionalInterface is used, it will throw an appropriate error message.

However, usage of the annotation is completely optional.

How Java 8 made these functional interfaces cooler?

Before Java 8, to define a functional interface we used to have to write verbose looking anonymous classes.

Here’s a snippet of how that used to look:

Yeah, I know. šŸ¤®

But with the introuction of lambdas ( which is just a shotform to represent a specific type of anonymous classes ā†’ Functional interfaces) here’s how the code would look

Pretty neat, eh?

Now, that we know what functional interfaces are, let’s look at some amazing in-built JDK 8 ones.

Exploring java.util.function package

Tool, Pliers, Screwdriver, Construction, Equipment

This package contains several nifty functional interfaces that forms the basis for functional programming in Java.

These functional interfaces have commonly used applications. Hence, it makes senses to use them.

Most of the times, these are syntactical sugars that makes coding a little bit more pleasant thanks to functional approach to things.

Let’s begin with most simple of them.

Java Predicate

What is a Java Predicate?

Java Predicate is a built-in functional interface that accepts a single argument and returns a “boolean” (not Boolean) value result.

Did not get it?

Let me show with an example:

Suppose you want to check if a number is greater than 0. Here’s how a predicate would look like:

Predicate<Integer> checkIfPositive = num -> (num > 0);
System.out.println(checkIfPositive.test(2)); // Output: true

When this predicate is called at line 2, it will return true/false

What is this test method that is being called in predicate? Let’s quickly glance over some poplar predicate method then it will make better sense.

Popular Predicate methods

  • test (the single abstract method for Predicate)

Remember how we discussed functional methods can have a single abstract method? Well, “test” is that method for Predicate.

It evaluates the results and returns true or false. Like in the initial example it will print true.

  • and

It represents the logical AND (like && in Java) for predicate outputs.

So, since predicate return true and false, joining them with “and” will feel like adding && conditions.

Predicate<Integer> checkIfPositive = num -> (num > 0);
Predicate<Integer> checkIfSmallerThan100 = num -> (num < 100);

System.out.println(checkIfPositive.and(checkIfSmallerThan100).test(60)); // Output: true

In the above example, the “and” operator will logically work as ” checkIfPositive AND checkIfSmallerThan100″

This particular case might be helpful in validating if valid age

  • or

It represents the logical OR operator (like || in Java) for predicate outputs.

Let’s look below:

Predicate<Integer> greaterThan18 = num -> (num > 18);
Predicate<Integer> lessThan64 = num -> (num < 64); 

System.out.println(lessThan64.or(greaterThan18).test(21)); // Output: true

In the above example, the “or” operator will logically work as ” lessThan64 OR greaterThan18″

But why would we even use them?

Common uses for Predicate

List, Icon, Symbol, Paper, Sign, Flat, Note, Form, Mark
  • Streams allMatch and anyMatch

Predicate gets even powerful with Java 8’s streams.

Suppose you are getting list of Persons from a list somewhere and you need to verify if all the Person have required access to continue for some resource.

Here’s how that will look with Predicate:

class Person { // Point 1 

		//assume getters and setters
		private Integer pkId;
		private String name; 
		private String accessLevel;

		public String getAccessLevel() {
				return this.accessLevel;

public class MainApp {

   public static void main(String[] args) {

	  List<Person> allPeople = getListOfPersons);
	  Predicate<Person> checkAccessLevel = p ->  p.getAccessLevel().equals("ADMIN");
	  boolean toAllow =; // Point 2
		System.out.println("Denied access");


Point 1: Let this dummy Person object serve as our model. Note, that we are assuming that the private variables have properly named getters and setters

Point 2: In combination with streams, Predicate help us avoid boilerplate code to iterate through each object and do the string comparison.

  • Validation pattern
Detective, Searching, Man, Search, Magnifying

There’s a very innovative validator pattern implementation at Dzone that clearly explains the validation pattern.

Here’s the cookie cutter version:

Suppose there are several serious of validation you need to do at the backend(most commonly done for forms).

In that case instead of writing code like this:

Snippet from DZone

We can move to writing validations like this:

Snippet from DZone

Now what’s the profit?

  1. The validations are legible and there’s a reduction in redundant verbose code
  2. We can keep on dynamically adding more conditions and they would still be understandable, thanks to, separation done by “and”/”or” methods

If you are still here, congratulations! Now let’s enter Functions. Not that kind, the interfaces kind.


Similar to Predicate, there are BiPredicate.

They, like the name implies, accept two instead of one parameter and follow all the other characteristics of Predicate.

Java Function Interface

What is a Java Function interface?

It represents a functional interface that accepts one argument and produces a result of any class type.

We know that functional interfaces can have a single abstract method (SAM). So the SAM here is called apply:

  • apply Applies this function to the given argument.

Let’s look at an example:

The following code, performs cube root of 27.

Function<Integer, Double> cubeRoot = num -> Math.pow(num , 0.334);
System.out.println(cubeRoot.apply(27)); // Output: 3

<Integer, Double> represents parameter and output type respectively.

The next obvious question should be:

Why use Java Function interface instead of a normal method?

People, Special, Different, Employment, Hiring, Job
  1. Streams: If you have made any list manipulation in Java recently, you would have used streams.

Let’s look at the code below that extracts name from Person class (defined above)

   .map(p -> p.getName())

This code extracts list of names from list of Persons.

Now, notice the map () method.

It is receiving p ā†’ p.getName() as a parameter…Isn’t that weird?

This is made possible with Java Function. map () receives Function (the functional interface) as a parameter and since pā†’ p.getName() is a function so that is how it is possible.

  1. Chained Function

Similar to Predicate chaining, you can chain two functions also. By this, result of one function is then passed to another. You can have two independent functions and can reuse them to create a new use-case.

Let’s explore this with an example.

Function<Double, Double> getCubeRoot = num -> Math.pow(num, 0.334);
Function<Double, Double> getSqaureRoot = num -> Math.pow(num, 0.5);

System.out.println(getCubeRoot.apply(getSqaureRoot.apply(64d))); //Output: 2

As we can see, getCubeRoot and getSqaureRoot has individual use cases to get the cube and square root respectively, but then they can be used together to get the sixth root. (1/2 x 1/3)

Why use predicate when we have Function?

The next obvious question, would be why not use Function everywhere? Isn’t Predicate just a special case of Function, where it returns Boolean value?

Well, no.

Let me explain.

A function can return a “Boolean” (wrapper class) value, while Predicate will always return a “boolean” (primitive type) value.

So, there is a lack of autoboxing in Predicate and a lack of unboxing in Function.

Box, Package

This is intentionally done because Predicate, in most cases, is used for validations. Therefore if Predicate returns null instead of true/false, we won’t know what should be the course of action. In Streams, there are filter functions that can only accept Predicates whereas a map will only accept a Function.

This can be easily demonstrated. The following code will return in complication error:

Function<Person, Boolean> isAdult = user -> user.getAge() > 18;
Stream<Person> beerPartyList  = stringList().stream().filter(isAdult); // point 1

Predicate<Person> isAwesome = user -> user.following().equals("mukundmadhav02");
Stream<Person> awesomePeopleList = stringList().stream().map(isAwesome); // point 2

At point 1 and 2, we are passing Function, where a predicate is needed and vice versa.

Let’s discuss more on Java on Twitter šŸŽƒ

I do post lot of memes too

What is a BiFunction?

Like Predicate, there are BiFunctions also. As the name implies, they accept two arguments of any class type and return a single result.

Java Supplier and Consumer

The Java util Supplier and Consumer functional interfaces are very much complementary to each other so it’s better if we look at them together.

First, let’s get their definitions out of the way.

What is Java Consumer interface?

Shopping, Cart, Man, Woman, Running, Run, Buy, Store

It is :

  • a Functional interface šŸ’šŸ»
  • Accepts one argument and does not return anything
  • Single Abstract Method: accept – It simply performs the operation

Hm, let’s look at an example:

In the following code, we are accepting a String in our Consumer. And, we are then returning the greeting message.

Consumer<String> generateGreeting = name -> System.out.println("Hello " + name);
generateGreeting.accept("Mukund"); // Output: Hello Mukund

name is passed as an argument to generateGreeting consumer, and output is printed.

Pretty simple, eh?

Where would you use the Consumer interface?

The one area Consumer are ubiquitously used is in forEach function.

List<String> coolWebsites = List.of("colorhexpicker", "google");
coolWebsites.forEach(site -> System.out.println("https://" + site +  ".com"));

In the above example, we are iterating through list of cool websites and appending the HTTPS protocol and .com TLD before printing.

Iterating through each element via traditional for-loop involves too much boiler plating.

List’s forEach method accepts a Consumer and that makes iteration so simple and intuitive šŸ’«

Side Note:

If you look at the official Consumer documentation it says,

Unlike most other functional interfaces, Consumer is expected to operate via side effects.

What does this mean?

If we look at what consumer is doing, it is accepting something and not returning anything. Of the most useful things we can do with it, Consumer is typically most used in a for-each loop. So if we say that, for Each cannot mutate the list it is being passed to then, looping through each element in the list would be useless.

So that is why the consumer (unlike other functional interfaces) fails to satisfy the criteria for pure functions (which states that function should not mutate state).

Not important, but good to know.

Okay, let’s dive head-first Supplier.

What is Java Supplier interface?

Team, Greeting, Suppliers, Force, Collaboration

Just contrary to Consumers, Suppliers return a single output without any input.

It is:

  • a Functional interface
  • Does not accept anything, returns a single parameter
  • SAM: get: Gets a result

Hm, let’s look at an example

Supplier<String> getFavString = () -> "02";
System.out.println(getFavString.get()); // Output: 02

In the above example, a supplier getFavString does not accept anything but simply returns a string (02 in this case).

Where would you use the Supplier interface?

Now, if you are seeing a Supplier for the first time then like me, You might be tempted to think, why would anyone use it?

It does not accept anything and returns something…? Why would you use it?

Supplier becomes immensely useful when you tie it with another Java 8’s feature – Optional class. To briefly sum up what is Optional, it is a new class defined to Java 8 that helps in better handling of Null values.

  • Optional.orElseGet

Here, if the value is null, orElseGet invokes another function that can get the value or initialize with some default value.

String lastUser = Optional.of(getLatestNameFromChildDb())
														.orElseGet(() -> getLatestNameFromMasterDb());

In the above example, we first try to get value from Child DB and if that does not return anything then we get value from master DB.

() ā†’ getLatestNameFromMasterDb() is the supplier and its lazily executed. Meaning, it won’t get executed if we get value from get getLatestNameFromChildDb();

There is also Double Supplier and Double Supplier and So on. They are simply Suppliers/Consumers who return/accept Double valued results. The same is there for other types like IntSupplier, LongSupplier, etc.

Callbacks in Java

Icons, Phone, Round, Connect, Service, Sign, Support

With the introduction of functional interfaces in Java, another interesting programming practise is now possible in Java – callbacks.

Before diving into how callbacks are implemented in Java, let’s look at what they are and why are they useful?

Callback are function passed to another function. This allows for them to be excuted from the function they are passed to depending on certain criteria.

You’ll already know that it’s not possible to pass function to function in Java. This is where functional interfaces come in. Since they can be assigned agaisnt a variable, you can pass them to other functions and hence achieve calbacks.

public void init(Consumer<Void> callback) {

init((Void) -> done());

Let’s connect

Since you have read this far, let’s do a shameless plug.

Let’s discuss more on Java on Twitter šŸŽƒ

I do post lot of memes too

Let’s connect on Twitter for more Java and engineering jokes šŸ˜€ in general: @mukundmadhav02

TLDR Infographics

If you skipped the article or read it and want a summed up version, the following infographic summarises the entire article – with functional interfaces and Predicate, function, Consumer and Supplier.

Summary of Functional Interfaces in Java

Summing it up

To sum it up, functional interfaces enabled unparalleled functional programming in Java. Thanks to functional interfaces, Java programs are no longer declarative just for it.

Functional interfaces with lambdas, help to quickly spin up code execution This reduces the effort that would have otherwise been used to write long boilerplate code to more intuitive approaches.

All functional interfaces in Java compared

Hope this was helpful. Have an amazing day/night ahead šŸ˜Ž

Leave a Comment