PYnative

Python Programming

  • Learn Python ▼
    • Python Tutorials
    • Python Basics
    • Python Interview Q&As
  • Exercises ▼
    • Python Exercises
    • C++ Exercises
    • C Programming Exercises
    • Java Exercises
    • C# Exercises
  • Quizzes
  • Online Compilers▼
    • Online Python Code Editor
    • Online C Compiler
    • Online C++ Compiler
    • Online Java Compiler
    • Online C# Compiler
Home » Java Exercises » Java Lambda and Functional Interfaces Exercises: 25 Coding Problems with Solutions

Java Lambda and Functional Interfaces Exercises: 25 Coding Problems with Solutions

Updated on: July 6, 2026 | Leave a Comment

This collection of 25 Java exercises takes you from your very first lambda expression through the full set of built-in functional interfaces and their use inside the Stream API.

  • Early exercises cover writing lambdas for custom and built-in interfaces like Runnable and Comparator
  • The middle set works through Predicate, Function, Supplier, and Consumer, including composing and chaining them with and(), negate(), and andThen().
  • The later exercises apply all of this inside stream pipelines: map(), filter(), reduce(), flatMap(), partitioningBy(), and BiConsumer for iterating maps.

Each exercise includes a Problem Statement, Purpose, Hint, and a Solution with Explanation that breaks down exactly what each lambda is doing and why.

  • Also, See: Java Exercises with over 20+ topic-wise sets and 575+ coding questions to practice.
  • Practice questions using our Online Java Compiler
+ Table of Contents (25 Exercises)

Table of contents

  • Exercise 1: Basic Lambda Implementation
  • Exercise 2: Sorting Strings
  • Exercise 3: Runnable Lambda
  • Exercise 4: Math Operation Interface
  • Exercise 5: Check for Empty
  • Exercise 6: List Iteration
  • Exercise 7: Custom Comparator
  • Exercise 8: Filtering via Lambda
  • Exercise 9: Map to Lengths
  • Exercise 10: Filter Even Numbers
  • Exercise 11: Sum of Squares (Reduce)
  • Exercise 12: Collect to Map
  • Exercise 13: Distinct and Sorted
  • Exercise 14: String Joining
  • Exercise 15: FlatMap Example
  • Exercise 16: Find First Match
  • Exercise 17: Partitioning Data
  • Exercise 18: Predicate Filtering
  • Exercise 19: Chained Predicates
  • Exercise 20: Function Transformation
  • Exercise 21: Function Chaining
  • Exercise 22: Supplier Factory
  • Exercise 23: Supplier Randomizer
  • Exercise 24: Consumer Printing
  • Exercise 25: BiConsumer Mapping

Exercise 1: Basic Lambda Implementation

Problem Statement: Write a lambda expression that implements a custom functional interface StringProcessor with a method String process(String s), which converts a given string to uppercase.

Purpose: This exercise helps you practice writing a lambda expression that targets a custom functional interface, the foundation every other lambda exercise builds on.

Given Input: "hello world"

Expected Output: HELLO WORLD

▼ Hint

@FunctionalInterface marks an interface with exactly one abstract method, so a lambda can supply the body for process() directly, taking a String parameter and returning it converted with toUpperCase().

▼ Solution & Explanation
@FunctionalInterface
interface StringProcessor {
    String process(String s);
}

public class Main {
    public static void main(String[] args) {
        // Usage:
        StringProcessor toUpperCase = s -> s.toUpperCase();
        String result = toUpperCase.process("hello world");
        System.out.println(result);
    }
}Code language: Java (java)

Explanation:

  • @FunctionalInterface: Marks the interface as having exactly one abstract method, so the compiler can verify it is a valid target for a lambda.
  • s -> s.toUpperCase(): Lambda expression implementing process(), taking a String parameter and returning it converted to uppercase.
  • toUpperCase.process("hello world"): Invokes the lambda body just like calling any interface method.
  • Alternative: You could write the lambda with an explicit block body, s -> { return s.toUpperCase(); }, though the expression form is shorter for a single statement.

Exercise 2: Sorting Strings

Problem Statement: Given a list of names, use List.sort() and a lambda expression to sort them in reverse alphabetical order.

Purpose: This exercise helps you practice supplying a Comparator as a lambda instead of writing a separate class, a pattern used constantly when sorting collections.

Given Input: ["Charlie", "Alice", "Bob", "Diana"]

Expected Output: [Diana, Charlie, Bob, Alice]

▼ Hint
  • List.sort() takes a Comparator, and a lambda can supply that comparator directly without a separate class.
  • compareTo() returns a negative, zero, or positive value depending on order.
  • Swapping the arguments, b.compareTo(a) instead of a.compareTo(b), flips ascending order into descending order.
▼ Solution & Explanation
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("Charlie", "Alice", "Bob", "Diana"));

        // Usage:
        names.sort((a, b) -> b.compareTo(a));
        System.out.println(names);
    }
}Code language: Java (java)

Explanation:

  • names.sort((a, b) -> b.compareTo(a)): Supplies a Comparator<String> as a lambda, where a and b represent any two elements being compared.
  • b.compareTo(a): Reverses the natural ordering of String.compareTo(), producing descending alphabetical order.
  • in-place sorting: List.sort() mutates the original list directly rather than returning a new one.
  • Alternative: You could use names.sort(Comparator.reverseOrder()) for the same result without writing the comparison logic yourself.

Exercise 3: Runnable Lambda

Problem Statement: Create and start a new thread using a lambda expression instead of an anonymous Runnable class.

Purpose: This exercise helps you practice recognizing that Runnable is already a functional interface, so its usual anonymous class implementation can be replaced with a lambda.

Expected Output: Running in a separate thread

▼ Hint

Runnable has a single abstract method, run(), which takes no arguments and returns nothing, so a no-argument lambda like () -> ... can be passed directly to the Thread constructor.

▼ Solution & Explanation
public class Main {
    public static void main(String[] args) throws InterruptedException {
        // Usage:
        Thread thread = new Thread(() -> System.out.println("Running in a separate thread"));
        thread.start();
        thread.join();
    }
}Code language: Java (java)

Explanation:

  • () -> System.out.println(...): Lambda implementing Runnable‘s single abstract method run(), taking no arguments since run() itself takes none.
  • new Thread(() -> ...): Passes the lambda directly to the Thread constructor, which normally expects a Runnable object.
  • thread.join(): Waits for the thread to finish before main() exits, guaranteeing the output is printed.
  • Alternative: You could still write out an anonymous Runnable class with new Runnable() { public void run() {...} }, but the lambda form removes that boilerplate entirely.

Exercise 4: Math Operation Interface

Problem Statement: Create a functional interface MathOperation with int operate(int a, int b). Use lambdas to implement addition, subtraction, and multiplication.

Purpose: This exercise helps you practice assigning several different lambdas to variables of the same functional interface type, then invoking them interchangeably.

Given Input: a = 10, b = 5

Expected Output:

Addition: 15
Subtraction: 5
Multiplication: 50
▼ Hint
  • Declare operate(int a, int b) as the single abstract method inside MathOperation.
  • Assign a different lambda to a MathOperation variable for each arithmetic operation.
  • Call operate() on each variable the same way, regardless of which operation it holds.
▼ Solution & Explanation
@FunctionalInterface
interface MathOperation {
    int operate(int a, int b);
}

public class Main {
    public static void main(String[] args) {
        // Usage:
        MathOperation addition = (a, b) -> a + b;
        MathOperation subtraction = (a, b) -> a - b;
        MathOperation multiplication = (a, b) -> a * b;

        System.out.println("Addition: " + addition.operate(10, 5));
        System.out.println("Subtraction: " + subtraction.operate(10, 5));
        System.out.println("Multiplication: " + multiplication.operate(10, 5));
    }
}Code language: Java (java)

Explanation:

  • (a, b) -> a + b: Lambda implementing operate() to return the sum of its two parameters.
  • MathOperation addition = ...: Assigns the lambda to a variable typed as the functional interface, so it can be passed around and invoked like any object.
  • addition.operate(10, 5): Calls the lambda body with the given arguments, exactly as if operate() had been implemented by a full class.
  • Alternative: You could write a single method that accepts a MathOperation parameter and applies it, letting the caller decide which lambda to pass in at call time.

Exercise 5: Check for Empty

Problem Statement: Write a lambda expression to check if a given string is empty or null.

Purpose: This exercise helps you practice using the built-in Predicate functional interface, which is designed exactly for a method that takes one argument and returns a boolean.

Given Input: "", null, "hello"

Expected Output:

true
true
false
▼ Hint

Use Predicate<String> and check s == null || s.isEmpty(), relying on short-circuit evaluation so isEmpty() is never called on a null reference.

▼ Solution & Explanation
import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        // Usage:
        Predicate<String> isEmpty = s -> s == null || s.isEmpty();

        System.out.println(isEmpty.test(""));
        System.out.println(isEmpty.test(null));
        System.out.println(isEmpty.test("hello"));
    }
}Code language: Java (java)

Explanation:

  • Predicate<String>: A built-in functional interface with a single abstract method test(T t) that returns a boolean.
  • s == null || s.isEmpty(): Checks for null first using short-circuit evaluation, so isEmpty() is never called on a null reference.
  • isEmpty.test(...): Invokes the lambda for each input string, returning true for both an empty string and a null reference.
  • Alternative: You could use s == null || s.isBlank() instead, which also treats a string containing only whitespace as empty.

Exercise 6: List Iteration

Problem Statement: Use the forEach() method and a lambda expression to print all elements of an ArrayList<Integer> multiplied by 2.

Purpose: This exercise helps you practice using forEach() with a Consumer lambda, a shorter alternative to writing an explicit for loop when you only need to act on each element.

Given Input: [1, 2, 3, 4, 5]

Expected Output:

2
4
6
8
10
▼ Hint

forEach() takes a Consumer<T> lambda that receives each element in turn, so multiply the value by 2 inside the lambda body right before printing it.

▼ Solution & Explanation
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));

        // Usage:
        numbers.forEach(n -> System.out.println(n * 2));
    }
}Code language: Java (java)

Explanation:

  • numbers.forEach(...): Calls the given lambda once for every element in the list, in iteration order.
  • n -> System.out.println(n * 2): Lambda implementing Consumer<Integer>, doubling each element before printing it.
  • no return value: forEach() expects a Consumer, whose single abstract method accept() returns void, matching the lambda’s void body.
  • Alternative: You could use a stream instead, numbers.stream().map(n -> n * 2).forEach(System.out::println), which separates the doubling step from the printing step.

Exercise 7: Custom Comparator

Problem Statement: Given a list of Employee objects (with name and salary attributes), write a lambda expression to sort them by salary in ascending order.

Purpose: This exercise helps you practice writing a Comparator lambda that compares a specific field of a custom object, rather than the object’s natural ordering.

Expected Output: [Bob: 55000.0, Charlie: 65000.0, Alice: 75000.0]

▼ Hint
  • Implement Comparator<Employee> as a lambda taking two Employee parameters, e1 and e2.
  • Use Double.compare(e1.getSalary(), e2.getSalary()) instead of subtracting the salaries directly, since subtraction can lose precision with doubles.
  • Pass the lambda straight into employees.sort() to reorder the list in place.
▼ Solution & Explanation
import java.util.ArrayList;
import java.util.List;

class Employee {
    private final String name;
    private final double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    @Override
    public String toString() {
        return name + ": " + salary;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>(List.of(
                new Employee("Alice", 75000),
                new Employee("Bob", 55000),
                new Employee("Charlie", 65000)
        ));

        // Usage:
        employees.sort((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        System.out.println(employees);
    }
}Code language: Java (java)

Explanation:

  • (e1, e2) -> Double.compare(...): Lambda implementing Comparator<Employee>‘s compare() method, comparing two employees by their salary field.
  • Double.compare(a, b): Returns a negative, zero, or positive value based on the natural ordering of the two doubles, exactly what sort() needs.
  • employees.sort(...): Reorders the list in place using the comparator, from lowest salary to highest.
  • Alternative: You could use employees.sort(Comparator.comparingDouble(Employee::getSalary)) for a shorter, method reference based version of the same comparator.

Exercise 8: Filtering via Lambda

Problem Statement: Write a method that takes a list of integers and a custom lambda condition to filter out numbers that don’t match the condition (e.g., filter out odd numbers).

Purpose: This exercise helps you practice accepting a Predicate as a method parameter, which lets the caller supply any filtering rule without the method needing to know it in advance.

Given Input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Expected Output: Even numbers: [2, 4, 6, 8, 10]

▼ Hint
  • Accept a Predicate<Integer> parameter in the method signature so the caller can supply any condition.
  • Loop through the input list and call condition.test(number) for each element.
  • Add the element to the result list only when the condition returns true.
▼ Solution & Explanation
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class Main {

    public static List<Integer> filterNumbers(List<Integer> numbers, Predicate<Integer> condition) {
        List<Integer> result = new ArrayList<>();
        for (int number : numbers) {
            if (condition.test(number)) {
                result.add(number);
            }
        }
        return result;
    }

    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // Usage:
        List<Integer> evenNumbers = filterNumbers(numbers, n -> n % 2 == 0);
        System.out.println("Even numbers: " + evenNumbers);
    }
}Code language: Java (java)

Explanation:

  • Predicate<Integer> condition: Lets the method accept any single-argument boolean check as an argument, without knowing in advance what that check will be.
  • condition.test(number): Evaluates the caller’s lambda against the current element.
  • n -> n % 2 == 0: The specific lambda passed in at the call site, keeping only even numbers.
  • Alternative: You could rewrite filterNumbers() using a stream, numbers.stream().filter(condition).collect(Collectors.toList()), which removes the manual loop and result list entirely.

Exercise 9: Map to Lengths

Problem Statement: Given a list of strings, use map() to create a new list containing the length of each string.

Purpose: This exercise helps you practice using the stream map() operation to transform every element of a collection into a different type or value.

Given Input: ["apple", "banana", "kiwi", "grape", "fig"]

Expected Output: Lengths: [5, 6, 4, 5, 3]

▼ Hint

map() transforms each stream element with the given function, so passing the String::length method reference converts every string into its length.

▼ Solution & Explanation
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<String> words = List.of("apple", "banana", "kiwi", "grape", "fig");

        // Usage:
        List<Integer> lengths = words.stream()
                                      .map(String::length)
                                      .collect(Collectors.toList());
        System.out.println("Lengths: " + lengths);
    }
}Code language: Java (java)

Explanation:

  • words.stream(): Converts the list into a Stream<String> that supports functional style operations.
  • .map(String::length): Applies String‘s length() method to every element, producing a new stream of Integer values.
  • .collect(Collectors.toList()): Gathers the transformed stream elements back into a concrete List<Integer>.
  • Alternative: You could write the mapping step as a lambda instead of a method reference, .map(s -> s.length()), which behaves identically but is slightly more verbose.

Exercise 10: Filter Even Numbers

Problem Statement: Given a list of integers, use filter() to extract all even numbers into a new list.

Purpose: This exercise helps you practice using the stream filter() operation to keep only the elements that satisfy a given condition.

Given Input: [11, 22, 33, 44, 55, 66, 77, 88]

Expected Output: Even numbers: [22, 44, 66, 88]

▼ Hint

filter() keeps only the elements for which the given predicate returns true, so testing n % 2 == 0 keeps just the even numbers in the resulting stream.

▼ Solution & Explanation
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(11, 22, 33, 44, 55, 66, 77, 88);

        // Usage:
        List<Integer> evenNumbers = numbers.stream()
                                            .filter(n -> n % 2 == 0)
                                            .collect(Collectors.toList());
        System.out.println("Even numbers: " + evenNumbers);
    }
}Code language: Java (java)

Explanation:

  • numbers.stream(): Converts the list into a stream so intermediate operations like filter() can be chained.
  • .filter(n -> n % 2 == 0): Keeps only the elements where the modulo check evaluates to true, discarding everything else.
  • .collect(Collectors.toList()): Terminates the stream and gathers the surviving elements into a new List<Integer>.
  • Alternative: You could use numbers.stream().filter(n -> n % 2 == 0).toList() (Java 16+) instead of Collectors.toList(), which returns an unmodifiable list with less boilerplate.

Exercise 11: Sum of Squares (Reduce)

Problem Statement: Given a list of integers, use map() and reduce() to find the sum of the squares of all elements.

Purpose: This exercise helps you practice combining map() with reduce(), transforming each element before folding the whole stream down into a single accumulated value.

Given Input: [1, 2, 3, 4, 5]

Expected Output: Sum of squares: 55

▼ Hint
  • map(n -> n * n) transforms every element into its square before reduction happens.
  • reduce(identity, accumulator) combines all stream elements into a single value, starting from the identity.
  • Integer::sum is a method reference that adds two integers together, matching the accumulator’s expected signature.
▼ Solution & Explanation
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5);

        // Usage:
        int sumOfSquares = numbers.stream()
                                   .map(n -> n * n)
                                   .reduce(0, Integer::sum);
        System.out.println("Sum of squares: " + sumOfSquares);
    }
}Code language: Java (java)

Explanation:

  • .map(n -> n * n): Squares every element in the stream before reduction takes place.
  • .reduce(0, Integer::sum): Starts from an identity value of 0 and repeatedly combines it with each squared value using addition.
  • Integer::sum: Method reference equivalent to the lambda (a, b) -> a + b.
  • Alternative: You could use .mapToInt(n -> n * n).sum() instead, which avoids boxing to Integer and returns a primitive int directly.

Exercise 12: Collect to Map

Problem Statement: Given a list of Person objects (with ID and Name), use collect() and Collectors.toMap() to convert the list into a Map<Integer, String>.

Purpose: This exercise helps you practice converting a stream of objects directly into a Map, choosing which field becomes the key and which becomes the value.

Expected Output: {1=Alice, 2=Bob, 3=Charlie}

▼ Hint

Collectors.toMap() takes two functions, one to derive the key from each element and one to derive the value, and returns a Map built from the stream.

▼ Solution & Explanation
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class Person {
    private final int id;
    private final String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Person> people = List.of(
                new Person(1, "Alice"),
                new Person(2, "Bob"),
                new Person(3, "Charlie")
        );

        // Usage:
        Map<Integer, String> idToName = people.stream()
                .collect(Collectors.toMap(Person::getId, Person::getName));
        System.out.println(idToName);
    }
}Code language: Java (java)

Explanation:

  • Collectors.toMap(Person::getId, Person::getName): Builds a map by applying the key function and value function to every stream element.
  • Person::getId / Person::getName: Method references supplying the key and value extraction logic without writing explicit lambdas.
  • resulting Map<Integer, String>: Keys come from each person’s ID, values come from their name.
  • Alternative: You could pass a third merge function argument to toMap(), (existing, replacement) -> existing, to handle the case where two people share the same ID instead of throwing an exception.

Exercise 13: Distinct and Sorted

Problem Statement: Given a list of numbers with duplicates, use a stream to remove duplicates, sort them in ascending order, and collect them into a list.

Purpose: This exercise helps you practice chaining multiple intermediate stream operations, distinct() and sorted(), before collecting the final result.

Given Input: [5, 3, 8, 3, 1, 5, 9, 1]

Expected Output: [1, 3, 5, 8, 9]

▼ Hint
  • distinct() removes duplicate values by relying on equals(), keeping only the first occurrence of each value.
  • sorted() with no arguments orders the remaining elements using their natural ordering.
  • Chain both operations before collect() so duplicates are removed first and the smaller remaining set is then sorted.
▼ Solution & Explanation
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(5, 3, 8, 3, 1, 5, 9, 1);

        // Usage:
        List<Integer> result = numbers.stream()
                                       .distinct()
                                       .sorted()
                                       .collect(Collectors.toList());
        System.out.println(result);
    }
}Code language: Java (java)

Explanation:

  • .distinct(): Removes duplicate elements, keeping just one copy of each distinct value.
  • .sorted(): Arranges the remaining elements in ascending natural order, since Integer implements Comparable.
  • operation order: Removing duplicates before sorting means sorted() has fewer elements to process.
  • Alternative: You could reverse the order and call .sorted().distinct(), which produces the same final result here, though sorting first can be less efficient on very large inputs since it still has to process every duplicate.

Exercise 14: String Joining

Problem Statement: Given a list of programming languages (e.g., ["Java", "Python", "C++"]), use a stream to join them into a single string separated by commas (e.g., "Java, Python, C++").

Purpose: This exercise helps you practice using Collectors.joining(), a purpose-built collector for combining stream elements into a single delimited string.

Given Input: ["Java", "Python", "C++"]

Expected Output: Java, Python, C++

▼ Hint

Collectors.joining(delimiter) concatenates every stream element into a single String, inserting the given delimiter between elements without adding one after the last.

▼ Solution & Explanation
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<String> languages = List.of("Java", "Python", "C++");

        // Usage:
        String joined = languages.stream()
                                  .collect(Collectors.joining(", "));
        System.out.println(joined);
    }
}Code language: Java (java)

Explanation:

  • Collectors.joining(", "): Concatenates the stream elements into one String, placing ", " between each pair of adjacent elements.
  • no trailing delimiter: joining() only inserts the delimiter between elements, so the final string never ends with one.
  • languages.stream().collect(...): The standard pattern for turning a stream back into a single collected result.
  • Alternative: You could pass a prefix and suffix too, Collectors.joining(", ", "[", "]"), to wrap the joined result in brackets like a formatted list.

Exercise 15: FlatMap Example

Problem Statement: Given a list of lists of integers List<List<Integer>>, use flatMap() to flatten the structure into a single List<Integer>.

Purpose: This exercise helps you practice using flatMap() to merge several nested streams into one continuous stream, instead of ending up with a stream of streams.

Given Input: [[1, 2, 3], [4, 5], [6, 7, 8, 9]]

Expected Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]

▼ Hint

flatMap() converts each inner list into its own stream with List::stream, then merges all of those streams into one continuous stream of individual elements.

▼ Solution & Explanation
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<List<Integer>> nestedNumbers = List.of(
                List.of(1, 2, 3),
                List.of(4, 5),
                List.of(6, 7, 8, 9)
        );

        // Usage:
        List<Integer> flatList = nestedNumbers.stream()
                                               .flatMap(List::stream)
                                               .collect(Collectors.toList());
        System.out.println(flatList);
    }
}Code language: Java (java)

Explanation:

  • nestedNumbers.stream(): Produces a Stream<List<Integer>>, where each element is itself a list.
  • .flatMap(List::stream): Converts each inner list into a stream of its elements, then flattens all of those streams into a single stream.
  • difference from map(): map() would produce a Stream<Stream<Integer>>, while flatMap() merges the nested streams into one flat stream automatically.
  • Alternative: You could achieve the same flattening with a nested for loop that adds every inner element to a new ArrayList, though that removes the declarative, single-pipeline style of the stream version.

Exercise 16: Find First Match

Problem Statement: Given a list of words, find the first word that starts with the letter ‘J’ using streams. Return an Optional<String>.

Purpose: This exercise helps you practice using a short-circuiting terminal operation, findFirst(), and handling its result safely with Optional instead of risking a null.

Given Input: ["Apple", "Banana", "Java", "Jasmine", "Kiwi"]

Expected Output: Java

▼ Hint
  • filter(word -> word.startsWith("J")) keeps only the words that begin with the target letter.
  • findFirst() is a short-circuiting terminal operation that stops as soon as it finds the first matching element, returning it wrapped in an Optional.
  • Optional.orElse(...) safely unwraps the result, supplying a fallback value if no word matched at all.
▼ Solution & Explanation
import java.util.List;
import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        List<String> words = List.of("Apple", "Banana", "Java", "Jasmine", "Kiwi");

        // Usage:
        Optional<String> firstJWord = words.stream()
                                            .filter(word -> word.startsWith("J"))
                                            .findFirst();
        System.out.println(firstJWord.orElse("No match found"));
    }
}Code language: Java (java)

Explanation:

  • .filter(word -> word.startsWith("J")): Narrows the stream down to only the words starting with “J”.
  • .findFirst(): Returns an Optional<String> containing the first surviving element, or an empty Optional if none matched.
  • firstJWord.orElse("No match found"): Safely reads the value out of the Optional without risking a NullPointerException.
  • Alternative: You could use .anyMatch(word -> word.startsWith("J")) if you only needed to know whether a match exists, without needing the actual matching word.

Exercise 17: Partitioning Data

Problem Statement: Use Collectors.partitioningBy() to split a list of integers based on whether each number is even or odd.

Purpose: This exercise helps you practice using partitioningBy(), which always produces a two-way split keyed by true and false, unlike the open-ended grouping of groupingBy().

Given Input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Expected Output:

Even: [2, 4, 6, 8, 10]
Odd: [1, 3, 5, 7, 9]
▼ Hint

Collectors.partitioningBy() takes a Predicate and returns a single Map<Boolean, List<T>>, where the true key holds elements matching the predicate and the false key holds everything else.

▼ Solution & Explanation
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // Usage:
        Map<Boolean, List<Integer>> partitioned = numbers.stream()
                .collect(Collectors.partitioningBy(n -> n % 2 == 0));

        System.out.println("Even: " + partitioned.get(true));
        System.out.println("Odd: " + partitioned.get(false));
    }
}Code language: Java (java)

Explanation:

  • Collectors.partitioningBy(n -> n % 2 == 0): Splits the stream into exactly two groups based on whether each element satisfies the predicate.
  • partitioned.get(true): Retrieves the list of elements for which the predicate returned true, in this case the even numbers.
  • always two keys: Unlike groupingBy(), partitioningBy() always produces both a true and a false entry, even if one of the lists ends up empty.
  • Alternative: You could use Collectors.groupingBy(n -> n % 2 == 0) instead, which behaves similarly here but is meant for grouping by an arbitrary classifier rather than a strict two-way split.

Exercise 18: Predicate Filtering

Problem Statement: Use a Predicate<String> to filter out strings from a list that start with the letter “A”.

Purpose: This exercise helps you practice negating a Predicate with negate() instead of rewriting the condition from scratch.

Given Input: ["Apple", "Banana", "Avocado", "Cherry", "Almond"]

Expected Output: [Banana, Cherry]

▼ Hint

Define the predicate to check for a match with startsWith("A"), then call negate() on it inside filter() to keep only the strings that do not match.

▼ Solution & Explanation
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<String> words = List.of("Apple", "Banana", "Avocado", "Cherry", "Almond");

        Predicate<String> startsWithA = word -> word.startsWith("A");

        // Usage:
        List<String> filtered = words.stream()
                                      .filter(startsWithA.negate())
                                      .collect(Collectors.toList());
        System.out.println(filtered);
    }
}Code language: Java (java)

Explanation:

  • Predicate<String> startsWithA = word -> word.startsWith("A"): Stores a reusable rule for matching strings that start with “A”.
  • startsWithA.negate(): Returns a new predicate that flips the result of the original, so filter() keeps only strings the original predicate rejects.
  • .filter(startsWithA.negate()): Removes every string that starts with “A”, leaving the rest.
  • Alternative: You could write the negated logic directly as a new lambda, word -> !word.startsWith("A"), instead of calling negate() on the original predicate.

Exercise 19: Chained Predicates

Problem Statement: Create two predicates, one checking if a number is greater than 10, and another checking if it’s even. Chain them using and() to filter a list.

Purpose: This exercise helps you practice combining two independent Predicate conditions into one using and(), instead of writing a single combined lambda from scratch.

Given Input: [4, 8, 11, 12, 15, 18, 20, 25]

Expected Output: [12, 18, 20]

▼ Hint
  • Store each condition as a separate Predicate<Integer> so they can be reused and combined independently.
  • Combine them with isGreaterThanTen.and(isEven), which returns a new predicate that only matches when both conditions are true.
  • Pass the combined predicate directly into filter().
▼ Solution & Explanation
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(4, 8, 11, 12, 15, 18, 20, 25);

        Predicate<Integer> isGreaterThanTen = n -> n > 10;
        Predicate<Integer> isEven = n -> n % 2 == 0;

        // Usage:
        List<Integer> result = numbers.stream()
                                       .filter(isGreaterThanTen.and(isEven))
                                       .collect(Collectors.toList());
        System.out.println(result);
    }
}Code language: Java (java)

Explanation:

  • isGreaterThanTen.and(isEven): Builds a new Predicate that only returns true when both the original predicate and isEven both return true for the same element.
  • short-circuit evaluation: and() stops checking as soon as the first predicate returns false, so isEven is never evaluated for numbers 10 or below.
  • .filter(isGreaterThanTen.and(isEven)): Applies the combined condition directly inside the stream pipeline.
  • Alternative: You could combine the two conditions inside a single lambda instead, n -> n > 10 && n % 2 == 0, though keeping them as separate named predicates makes each rule easier to reuse and test on its own.

Exercise 20: Function Transformation

Problem Statement: Use a Function<String, Integer> to parse a list of numeric strings (e.g., "123") into a list of integers.

Purpose: This exercise helps you practice using the general purpose Function<T, R> interface to represent a single-argument transformation, and applying it inside map().

Given Input: ["123", "45", "678", "9"]

Expected Output: [123, 45, 678, 9]

▼ Hint

Function<String, Integer> represents any single-argument transformation from a String to an Integer, so assigning Integer::parseInt to it lets map() use it directly to convert every string in the list.

▼ Solution & Explanation
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<String> numericStrings = List.of("123", "45", "678", "9");

        Function<String, Integer> parseToInt = Integer::parseInt;

        // Usage:
        List<Integer> numbers = numericStrings.stream()
                                               .map(parseToInt)
                                               .collect(Collectors.toList());
        System.out.println(numbers);
    }
}Code language: Java (java)

Explanation:

  • Function<String, Integer> parseToInt = Integer::parseInt: Stores a reusable transformation rule using a method reference instead of a lambda.
  • .map(parseToInt): Applies that transformation to every element in the stream, converting each numeric string into its Integer value.
  • Function<T, R>: The general purpose functional interface for any single-argument transformation, with apply() as its single abstract method.
  • Alternative: You could inline the transformation directly as .map(Integer::parseInt) without a separate named variable, which works identically here since it is only used once.

Exercise 21: Function Chaining

Problem Statement: Create one Function that doubles an integer, and another that adds 3 to it. Chain them using andThen() and apply them to a number.

Purpose: This exercise helps you practice composing two separate Function instances into one pipeline using andThen(), instead of writing a single combined lambda.

Given Input: 5

Expected Output: Result: 13

▼ Hint
  • doubleValue.andThen(addThree) builds a new Function that runs doubleValue first, then feeds its result into addThree.
  • The order matters: andThen() always runs the original function before the one passed as the argument.
  • Call combined.apply(5) just like any other Function to run both steps in sequence.
▼ Solution & Explanation
import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        Function<Integer, Integer> doubleValue = n -> n * 2;
        Function<Integer, Integer> addThree = n -> n + 3;

        // Usage:
        Function<Integer, Integer> combined = doubleValue.andThen(addThree);
        int result = combined.apply(5);
        System.out.println("Result: " + result);
    }
}Code language: Java (java)

Explanation:

  • doubleValue.andThen(addThree): Returns a composed Function that first applies doubleValue, then passes that output into addThree.
  • combined.apply(5): Runs doubleValue.apply(5) to get 10, then addThree.apply(10) to get 13.
  • execution order: andThen() always executes the calling function first and the argument function second, the opposite of compose().
  • Alternative: You could use addThree.compose(doubleValue) instead, which produces the exact same composed behavior since compose() runs its argument first and the calling function second.

Exercise 22: Supplier Factory

Problem Statement: Use a Supplier<List<String>> to instantly instantiate and return a new ArrayList.

Purpose: This exercise helps you practice using Supplier, the functional interface for producing a value with no input arguments, a common pattern for lazy or on-demand object creation.

Expected Output: [first item]

▼ Hint

Supplier<T> takes no arguments and simply produces a value when get() is called, so a constructor reference like ArrayList::new can be assigned to it directly to build a new list on demand.

▼ Solution & Explanation
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        Supplier<List<String>> listFactory = ArrayList::new;

        // Usage:
        List<String> newList = listFactory.get();
        newList.add("first item");
        System.out.println(newList);
    }
}Code language: Java (java)

Explanation:

  • Supplier<List<String>> listFactory = ArrayList::new: Stores a constructor reference that produces a brand new ArrayList each time it runs.
  • listFactory.get(): Calls the Supplier‘s single abstract method, creating and returning a fresh, empty list.
  • no input arguments: Unlike Function or Predicate, Supplier takes nothing in and only produces an output, matching what a factory method needs.
  • Alternative: You could write the lambda explicitly as () -> new ArrayList<>(), which behaves identically to the ArrayList::new constructor reference.

Exercise 23: Supplier Randomizer

Problem Statement: Write a Supplier<Integer> that generates a random number between 1 and 100 every time it is called.

Purpose: This exercise helps you practice writing a Supplier whose body produces a fresh value on every call, rather than one that always returns the same fixed result.

Expected Output:

42
7
95

(the exact numbers vary on every run)

▼ Hint

A Supplier<Integer> lambda can wrap random.nextInt(100) + 1 so calling get() runs that expression fresh each time, producing a new random value between 1 and 100 on every call.

▼ Solution & Explanation
import java.util.Random;
import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        Random random = new Random();
        Supplier<Integer> randomNumber = () -> random.nextInt(100) + 1;

        // Usage:
        System.out.println(randomNumber.get());
        System.out.println(randomNumber.get());
        System.out.println(randomNumber.get());
    }
}Code language: Java (java)

Explanation:

  • () -> random.nextInt(100) + 1: Lambda implementing Supplier<Integer>, generating a new random value between 1 and 100 every time it runs.
  • randomNumber.get(): Triggers the lambda body again on each call, so the returned value is never cached or reused.
  • shared Random instance: Reusing the same Random object across calls avoids the overhead of creating a new one every time.
  • Alternative: You could use ThreadLocalRandom.current().nextInt(1, 101) inside the lambda instead of a shared Random field, which avoids needing to manage the Random instance yourself.

Exercise 24: Consumer Printing

Problem Statement: Use a Consumer<String> to print a string prefixed with "Logged: ". Pass this consumer into a forEach loop on a list of messages.

Purpose: This exercise helps you practice writing a Consumer, the functional interface for an action that takes one argument and returns nothing, and reusing it across every element in a collection.

Given Input: ["Server started", "User logged in", "Cache cleared"]

Expected Output:

Logged: Server started
Logged: User logged in
Logged: Cache cleared
▼ Hint

Consumer<String> takes a single argument and returns nothing, which matches exactly what forEach() expects, so the same Consumer lambda can be defined once and reused for every element in the list.

▼ Solution & Explanation
import java.util.List;
import java.util.function.Consumer;

public class Main {
    public static void main(String[] args) {
        List<String> messages = List.of("Server started", "User logged in", "Cache cleared");

        Consumer<String> logMessage = message -> System.out.println("Logged: " + message);

        // Usage:
        messages.forEach(logMessage);
    }
}Code language: Java (java)

Explanation:

  • Consumer<String> logMessage = message -> System.out.println("Logged: " + message): Stores a reusable action that prefixes and prints any given string.
  • messages.forEach(logMessage): Calls the Consumer‘s accept() method once for every message in the list.
  • separation of concerns: Defining the Consumer separately from forEach() lets the same logging behavior be reused elsewhere without duplicating the print logic.
  • Alternative: You could inline the lambda directly inside forEach(), messages.forEach(message -> System.out.println("Logged: " + message)), which skips the named variable if the Consumer is only used once.

Exercise 25: BiConsumer Mapping

Problem Statement: Use a BiConsumer<String, Integer> to iterate through a Map<String, Integer> and print out key-value pairs formatted as "Key directly maps to Value".

Purpose: This exercise helps you practice using BiConsumer, the two-argument counterpart to Consumer, which matches exactly what Map.forEach() passes to its callback.

Given Input: {"Alice": 85, "Bob": 92, "Charlie": 78}

Expected Output:

Alice directly maps to 85
Bob directly maps to 92
Charlie directly maps to 78
▼ Hint
  • BiConsumer<T, U> takes two arguments and returns nothing, matching exactly what Map.forEach() passes to its callback: a key and a value.
  • Define the BiConsumer lambda with two parameters, key and value, then build the formatted message inside its body.
  • Pass the BiConsumer straight into Map.forEach(), which calls it once per entry in the map.
▼ Solution & Explanation
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiConsumer;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> scores = new LinkedHashMap<>();
        scores.put("Alice", 85);
        scores.put("Bob", 92);
        scores.put("Charlie", 78);

        BiConsumer<String, Integer> printMapping = (key, value) ->
                System.out.println(key + " directly maps to " + value);

        // Usage:
        scores.forEach(printMapping);
    }
}Code language: Java (java)

Explanation:

  • BiConsumer<String, Integer> printMapping: Stores a two-argument action, matching the key and value types stored in the map.
  • (key, value) -> System.out.println(...): Lambda implementing accept(), receiving both the key and the value for a single map entry.
  • scores.forEach(printMapping): Iterates the map and calls the BiConsumer once for every key-value pair, in insertion order since a LinkedHashMap is used.
  • Alternative: You could iterate manually with a for loop over scores.entrySet() and print each Map.Entry directly, though Map.forEach() with a BiConsumer avoids handling the Map.Entry object yourself.

Filed Under: Java Exercises

Did you find this page helpful? Let others know about it. Sharing helps me continue to create free Python resources.

TweetF  sharein  shareP  Pin

About Vishal

Image

I’m Vishal Hule, the Founder of PYnative.com. As a Python developer, I enjoy assisting students, developers, and learners. Follow me on Twitter.

Related Tutorial Topics:

Java Exercises

All Coding Exercises:

C Exercises
C++ Exercises
Python Exercises
Java Exercises
C# Exercises

Python Exercises and Quizzes

Free coding exercises and quizzes cover Python basics, data structure, data analytics, and more.

  • 15+ Topic-specific Exercises and Quizzes
  • Each Exercise contains 25+ questions
  • Each Quiz contains 25 MCQ
Exercises
Quizzes

Leave a Reply Cancel reply

your email address will NOT be published. all comments are moderated according to our comment policy.

Use <pre> tag for posting code. E.g. <pre> Your entire code </pre>

In: Java Exercises
TweetF  sharein  shareP  Pin

  Java Exercises

  • All Java Exercises
  • Java Exercise for Beginners
  • Java Loops Exercise
  • Java String Exercise
  • Java ArrayList Exercise
  • Java LinkedList Exercise
  • Java HashMap and TreeMap Exercise
  • Java HashSet and TreeSet Exercise
  • Java OOP Exercise
  • Java Methods Exercise
  • Java Enums Exercise
  • Java Exception Handling Exercise
  • Java File Handling Exercise
  • Java Date and Time Exercise
  • Java Data Structures Exercise
  • Java Sorting and Searching Exercise
  • Java Lambda and Functional Interfaces Exercise
  • Java Regex Exercise
  • Java Random Data Generation Exercise
  • Java Generics Exercise
  • Java Reflection Exercise
  • Java JDBC Exercise

All Coding Exercises

Python Exercises C Exercises C++ Exercises Java Exercises C# Exercises

About PYnative

PYnative.com is for Python lovers. Here, You can get Tutorials, Exercises, and Quizzes to practice and improve your Python skills.

Follow Us

To get New Python Tutorials, Exercises, and Quizzes

  • Twitter
  • Facebook
  • Sitemap

Explore Python

  • Learn Python
  • Python Basics
  • Python Databases
  • Python Exercises
  • Python Quizzes
  • Online Python Code Editor
  • Python Tricks

Coding Exercises

  • C Exercises
  • C++ Exercises
  • Python Exercises
  • Java Exercises
  • C# Exercises

Legal Stuff

  • About Us
  • Contact Us

We use cookies to improve your experience. While using PYnative, you agree to have read and accepted our:

  • Terms Of Use
  • Privacy Policy
  • Cookie Policy

Copyright © 2018–2026 pynative.com

Advertisement
Advertisement