top of page

How to Shift to Functional Programming in Java

Adopting Functional Programming is not only using the techniques of Functional Programming but understanding the conceptual difference between Object-Oriented Programming and start programming in a functional way. To understand what it is to program in a functional way, we have to know what declarative and imperative programming is.

  • There are two ways to program: declarative and imperative programming

  • Functional Programming is a type of declarative programming

  • Object-Oriented Programming is a type of imperative programming

Both are simply different approaches to solve the same problem.

Imperative vs Declarative Programming

Imperative programming is programming how to do something. It’s like a sequence of orders to modify the state. Think of how a for-loop works. You initialize a state and perform some kind of operation to change the state for each iteration. I’ll give you an example of imperative programming and compare it with a declarative programming version.

Assume say we have a list of names. We want a new list that only contains names longer than 5 characters after capitalizing them. Probably the most intuitive way is to use a for-loop.

public static List<String> getNames(List<String> nameList) {
    List<String> newNameList = new ArrayList<String>();
    for (int i =0; i < nameList.size(); i++) 
        String name = nameList.get(i);
        if (name.length() >5) {            
    return newNameList;

Let’s interpret what we did in the code above. We had to manually implement a way to iterate the list (using for loop), check each name’s length, capitalize and add it to the new list.

In other words, we iterated each element and

  • filtered the elements longer than 5 characters

  • converted the elements to uppercase, and

  • added the elements to a list.

What if there’s a way that automates the process using predefined functions? That’s how declarative programming works. Declarative programming is programming what to do instead of how. Processes such as filtering, converting, and adding can be represented as functions.

Java provides Stream API as a functional approach to process collections. A stream can iterate itself, so we don’t need to implement how to iterate the list. It works like a pipeline that can take in a number of operations.

The example above can be programmed declaratively using Stream API.

public static List<String> getNames2(List<String> nameList) 
    return nameList            
        .filter(name -> name.length() >5)            
        .map(name -> name.toUpperCase())            

Instead of implementing how to filter, convert, and add the elements in imperative programming, we can now use methods provided by the Stream API such as filter(), map(), and collect() and customize them by passing parameters. There are many more methods provided by the Stream API so I recommend looking them up.

I’ll quickly go over filter(), map(), and collect().


It takes a predicate as a parameter and returns a stream that matches the predicate. To recap, a predicate is a predefined functional interface that takes in one parameter and returns a boolean.

Here are some examples using filter():

// nums = [1, 2, 3, 4, 5, 6] -> num <3); -> num %10==0);

// names = ["Boy In Space", "Trevor Daniel", "Lil Peep", "Payday", "Joji"] -> name.startsWith("BOY")); -> name.contains("Space"));


It takes a Function as a parameter and returns a stream that applied the given function to each element. Here, Function is referring to a predefined functional interface that takes in one parameter and returns a value. Note that filter() and map() are examples of higher-order functions.

Here are some examples using map():

// nums = [1, 2, 3, 4, 5, 6] -> num *2)

// names = ["Boy In Space", "Trevor Daniel", "Lil Peep", "Payday", "Joji"] -> name.toLowerCase()) -> name.substring(2)) -> name.charAt(1))


It takes Collector as a parameter and puts the iterated elements in the Collector. We can use Collectors which are Collector implementation provided by Java.

Here are some examples using collect:

// nums = [1, 2, 3, 3, 5]
List<Integer> numList = -> num *2).collect(Collectors.toList());
// [4, 6, 6, 10]
Set<Integer> numSet = -> num *2).collect(Collectors.toSet());
// [4, 6, 10]

// names = ["Boy In Space", "Trevor Daniel", "Lil Peep", "Payday", "Joji"]
List<String> nameList = -> name.toLowerCase()).collect(Collectors.toList());
// [boy in space, trevor daniel, lil peep, payday, joji]
Set<String> nameSet = -> name.toLowerCase()).collect(Collectors.toSet());
// [lil peep, boy in space, joji, trevor daniel, payday]

If you noticed that what filter() and map() do is different from what collect() does, good catch! In fact, there are two types of Stream operations: terminal and non-terminal operation. Filter() and map() are examples of non-terminal operation and collect() is an example of terminal operation.

  • A Terminal operation returns a value.

  • A Non-Terminal operation, also called an intermediate operation, performs certain operations and returns a new stream. Many non-terminal operations take functional interface as their parameter and this is why we can use lambda expressions as used in the examples above.


Functional Programming isn’t simply using lambda expressions or Stream API to implement first-class functions or pure functions, but programming declaratively. According to Functional Thinking by Neal Ford, transitioning from imperative to declarative programming is “learning where to apply the higher-level abstractions and stop going immediately for detailed implementations.” It’s really about where and how to abstract your code to make your code more concise and precise. Indeed, Functional Programming isn’t always the answer to write good code. It is more important to understand the differences and be flexible to use both ways appropriately.

Source: Medium by Sohee Kim

The Tech Platform


Recent Posts

See All
bottom of page