September 18, 2021

flatMap() in Java Stream

When you use map operations in Java Stream resulting stream is obtained by applying the given function to all the elements of this stream. Java Stream API also provides a flatMap() method that apart from applying the given function to all the elements of this stream, flattens the resulting elements too so that all the nested elements are at the same level.

flatMap is an intermediate operation.

flatMap method signature

<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

Here mapper is a non-interfering, stateless function applied to each element of the stream.

R is the element type of the new stream.

flatMap Java examples

Let’s try to understand with few examples how flatMap() flattens the structure and how it helps.

Suppose there is an ArrayList that contains ArrayLists in turn and you want to count the total number of elements in the list. If you use map function then you will get the number of elements as 2 because map function will get each nested list as element of the stream.

public class FlatMapExample {
  public static void main(String[] args) {
    List<List<String>> list = Arrays.asList(Arrays.asList("a", "b"), Arrays.asList("c", "d"));
    long noOfElements = list.stream().map(a -> a.stream()).count();
    System.out.println("Number of elements- "+ noOfElements);
  }
}
Output
Number of elements- 2

When you use flatMap function, structure is flattened so that the number of elements is counted properly.

public class FlatMapExample {

  public static void main(String[] args) {
    List<List<String>> list = Arrays.asList(Arrays.asList("a", "b"), Arrays.asList("c", "d"));
    long noOfElements = list.stream().flatMap(a -> a.stream()).count();
    System.out.println("Number of elements- "+ noOfElements);
  }
}
Output
Number of elements- 4

If you want to display elements in nested ArrayLists in uppercase using map function returns List<Stream<String>>

public class FlatMapExample {
  public static void main(String[] args) {
    List<List<String>> list = Arrays.asList(Arrays.asList("a", "b"), Arrays.asList("c", "d"));
    List<Stream<String>> resultlist = list.stream()
                              .map(a -> a.stream()
                              .map(String::toUpperCase))
                              .collect(Collectors.toList());
    resultlist.forEach(a -> a.forEach(System.out::println));
  }
}

If you use flatMap in such scenario both lists are flattened so the return value is List

public class FlatMapExample {
  public static void main(String[] args) {
    List<List<String>> list = Arrays.asList(Arrays.asList("a", "b"), Arrays.asList("c", "d"));
    List<String> resultlist = list.stream()
                        .flatMap(a -> a.stream()
                        .map(String::toUpperCase))
                        .collect(Collectors.toList());
    resultlist.forEach(System.out::println);
  }
}
Output
A
B
C
D

Here is another example where we have a List of lists. There is a class Order which has a field items of type List. Now you want to display all the items in all the orders.

public class Order {
  private String orderId;
  private List<String> items;
  public String getOrderId() {
    return orderId;
  }
  public void setOrderId(String orderId) {
    this.orderId = orderId;
  }
  public List<String> getItems() {
    return items;
  }
  public void setItems(List<String> items) {
    this.items = items;
  }
}
public class FlatMapExample {
  public static void main(String[] args) {
    // Create list of orders
    List<Order> listOfOrders = new ArrayList<Order>();
    Order order = new Order();
    order.setOrderId("1");
    order.setItems(Arrays.asList("Book", "Shoes", "Watch"));
    listOfOrders.add(order);
    order = new Order();
    order.setOrderId("2");
    order.setItems(Arrays.asList("Mobile", "Book"));
    listOfOrders.add(order);

    List<String> listOfItems = listOfOrders.stream()
                          .flatMap(o -> o.getItems()
                          .stream())
                          .collect(Collectors.toList());
    listOfItems.forEach(System.out::println);
  }
}
Output
Book
Shoes
Watch
Mobile
Book

flatMap for primitive type stream

There are also flatMap() variants to be used for getting primitive type streams in Java.

  • flatMapToInt(Function<? super T,? extends IntStream> mapper)- Returns an IntStream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element.
  • flatMapToLong(Function<? super T,? extends LongStream> mapper)- Returns a LongStream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element.
  • flatMapToDouble(Function<? super T,? extends DoubleStream> mapper)- Returns a DoubleStream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element.

flatMapToInt Java example

Getting an IntStream by flattening a two dimensional array.

int[][] numbers = {{7,8}, {4,5}, {3,4}};
IntStream iStream = Stream.of(numbers).flatMapToInt(n -> Arrays.stream(n));
iStream.forEach(System.out::println);
Output
7
8
4
5
3
4

That's all for the topic flatMap() in Java Stream. If something is missing or you have something to share about the topic please write a comment.


You may also like

September 17, 2021

map() Function in Java With Examples

In this post we’ll see examples of map() function in Java Stream API that is used to apply some transformation to the elements in a stream. When you use a map operation a new stream is returned consisting of the resultant elements after applying the given function to all the elements of source stream.

Generalized map() function in java.util.stream.Stream interface is-

<R> Stream<R> map(Function<? super T,? extends R> mapper)

Here R is the element type of the new interface.

mapper is a non-interfering, stateless function applied to each element, mapper is of type Function which is a functional interface and can be implemented as a lambda expression.

Apart from the generalized map() function there are also methods mapToInt(), mapToLong(), and mapToDouble() returning IntStream, LongStream and DoubleStream respectively which are specialized primitive type streams for these primitive data types.

In the primitive type streams there is also a mapToObj() method which Returns an object-valued Stream.

map() Java Stream examples

1- Converting each element in a Stream in upper case and collecting those elements in a List. For this requirement map() method can be used to apply upperCase functionality to all the elements of the stream and then collect the result of this transformation into a List using collect() method.

List<String> names = Stream.of("Jack", "Lisa", "Scott", "Nikita", "Tony")
			   .map(s -> s.toUpperCase())
			   .collect(Collectors.toList());	
names.forEach(System.out::println);
Output
JACK
LISA
SCOTT
NIKITA
TONY

2- Using map() method to get a new Stream having only the selected fields from the source stream. That way you can transform the stream to have elements of new type.

Let’s say there is an Employee class with name, dept, age fields and the source stream contains objects of type Employee. Requirement is to map name and dept fields to a new stream of EmpDept type.

class Employee {
  private String name;
  private String dept;
  private int salary;

  Employee(String name, String dept, int salary){
    this.name = name;
    this.dept = dept;
    this.salary = salary;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public int getSalary() {
    return salary;
  }
  public void setSalary(int salary) {
    this.salary = salary;
  }
  public String getDept() {
    return dept;
  }
  public void setDept(String dept) {
    this.dept = dept;
  }
}

class EmpDept {
  private String name;
  private String dept;
  EmpDept(String name, String dept){
    this.name = name;
    this.dept = dept;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getDept() {
    return dept;
  }
  public void setDept(String dept) {
    this.dept = dept;
  }
}

public class EmpStream {
  public static void main(String[] args) {
    List<Employee> employeeList = new ArrayList<>(); 

    employeeList.add(new Employee("Jack", "Finance", 5500)); 
    employeeList.add(new Employee("Lisa", "Finance", 5600)); 
    employeeList.add(new Employee("Scott", "Finance", 7000));
    employeeList.add(new Employee("Nikita", "IT", 4500));
    employeeList.add(new Employee("Tony", "IT", 8000)); 

    List<EmpDept> emp = employeeList.stream()
             .map(e -> new EmpDept(e.getName(), e.getDept()))
             .collect(Collectors.toList());
    emp.forEach(e -> System.out.println("Name- " + e.getName() + 
                        " Department- " + e.getDept()));
  }
}
Output
Name- Jack Department- Finance
Name- Lisa Department- Finance
Name- Scott Department- Finance
Name- Nikita Department- IT
Name- Tony Department- IT

3- map() with filter example- Using map method to get a new stream having employee names in finance department. Filter method is used to filter those employees not matching the given predicate.

List<String> emp = employeeList.stream()
				.filter(e -> e.getDept().equals("Finance"))
				.map(e -> e.getName())
				.collect(Collectors.toList());
							   
emp.forEach(System.out::println);
Output
Jack
Lisa
Scott

Java mapToInt() example

1- If you want to get the average of salaries for the employees, using mapToInt() method you can get an IntStream consisting of salaries and then apply average() method on that int stream.

List<Employee> employeeList = new ArrayList<>(); 

employeeList.add(new Employee("Jack", "Finance", 5500)); 
employeeList.add(new Employee("Lisa", "Finance", 5600)); 
employeeList.add(new Employee("Scott", "Finance", 7000));
employeeList.add(new Employee("Nikita", "IT", 4500));
employeeList.add(new Employee("Tony", "IT", 8000)); 

double avgSalary = employeeList.stream()
                               .mapToInt(e -> e.getSalary())
                               .average()
                               .getAsDouble();
							   
System.out.println("Average salary- " + avgSalary);
Output
Average salary- 6120.0

2- If you want to get the maximum salary, using mapToInt() method you can get an IntStream consisting of salaries and then apply max() method on that int stream.

List<Employee> employeeList = new ArrayList<>(); 

employeeList.add(new Employee("Jack", "Finance", 5500)); 
employeeList.add(new Employee("Lisa", "Finance", 5600)); 
employeeList.add(new Employee("Scott", "Finance", 7000));
employeeList.add(new Employee("Nikita", "IT", 4500));
employeeList.add(new Employee("Tony", "IT", 8000)); 

int maxSalary = employeeList.stream()
                            .mapToInt(e -> e.getSalary())
                            .max()
                            .getAsInt();
							   
System.out.println("Maximum salary- " + maxSalary);
Output
Maximum salary- 8000

That's all for the topic map() Function in Java With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

September 16, 2021

Java Stream Collectors.teeing() Examples

In this tutorial we’ll see how to use Collectors.teeing() method which is added in Java 12 to the Collectors class in the Java Stream API.

Collectors.teeing() method

The teeing() method let you create a composite of two Collectors and there is also a third argument; a merging function. Every element passed to the method is processed by both downstream collectors, then their results are merged using the specified merge function into the final result.

Method syntax

public static <T,R1,R2,R> Collector<T,?,R> teeing(Collector<? super T,?,R1> downstream1, Collector<? super T,?,R2> downstream2, BiFunction<? super R1,? super R2,R> merger)

Here parameters are-

  • downstream1- the first downstream collector
  • downstream2- the second downstream collector
  • merger- the function which merges two results into the single one

Collectors.teeing() Java examples

1. Getting count and sum of elements in a List using Collectors.teeing function. By passing Collectors.counting() and Collectors.summingInt() as two downstream Collectors you can do the job of both counting the number of elements and getting the sum of elements in a single operation. Merging operation does the job of storing both sum and count in a List and returning that List.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TeeingDemo {

  public static void main(String[] args) {
     List<Integer> listOfNumbers = Arrays.asList(10, 25, 9, 87, 56);
     List<Integer> list = listOfNumbers.stream()
                          .collect(Collectors.teeing(
                             Collectors.counting(), 
                             Collectors.summingInt(n -> n), 
                             (count, sum) -> {
                                List<Integer> l = new ArrayList<>();
                                l.add(count.intValue());
                                l.add(sum);
                                return l;
                              }));
     System.out.println("Number of elements in the list- " + list.get(0));
     System.out.println("Sum of elements in the list- " + list.get(1));
  }
}
Output
Number of elements in the list- 5
Sum of elements in the list- 187

2. Getting average of elements in a List. Here with in the teeing method first Collector does the job of counting elements, second Collector does the job of getting the sum of elements and the merger operation does the job of calculating average.

import java.util.List;
import java.util.stream.Collectors;

public class TeeingDemo {

  public static void main(String[] args) {
     List<Integer> listOfNumbers = List.of(10, 25, 9, 87, 56);
     Double average = listOfNumbers.stream()
                        .collect(Collectors.teeing(
                           Collectors.counting(), 
                           Collectors.summingDouble(n -> n), 
                           (count, sum) -> sum/count));
     System.out.println("Average of elements in the list- " + average);
  }
}
Output
Average of elements in the list- 37.4

3. Using Collectors.teeing() to get the employees with the maximum and minimum salaries from the List of Employee objects.

Employee class used is as given below.

public class Employee{
  private String name;
  private String dept;
  private int salary;
  private int age;
  Employee(String name, String dept, int salary, int age){
    this.name = name;
    this.dept = dept;
    this.salary = salary;
    this.age = age;
  }
  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getDept() {
    return dept;
  }
  public void setDept(String dept) {
    this.dept = dept;
  }
  public int getSalary() {
    return salary;
  }
  public void setSalary(int salary) {
    this.salary = salary;
  }
}

To get the maximum and minimum values Collectors.maxBy() and Collectors.minBy() methods are used.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class TeeingDemo {

  public static void main(String[] args) {
    List<Employee> empList =  getEmployeeList();
    List<Optional<Employee>> list = empList.stream()
                       .collect(Collectors.teeing(
                         Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)), 
                         Collectors.minBy(Comparator.comparingInt(Employee::getSalary)), 
                            (emp1, emp2) -> {
                               List<Optional<Employee>> l = new ArrayList<>();
                                 l.add(emp1);
                                 l.add(emp2);
                                 return l;
                            }));
     System.out.println("Employee with max salary- " + (list.get(0).isPresent()? list.get(0).get().getName():null));
     System.out.println("Employee with min salary- " + (list.get(1).isPresent()? list.get(1).get().getName():null));
  }
  
    // Method to create list of employee objects
    private static List<Employee> getEmployeeList(){
        List<Employee> empList = Arrays.asList(new Employee("Ram", "IT", 12000, 34), 
                                       new Employee("Tina", "HR", 15000, 42), 
                                       new Employee("Roger", "IT", 9000, 25), 
                                       new Employee("Troy", "Accounts", 7000, 35));
        
        return empList;
    }
}
Output
Employee with max salary- Tina
Employee with min salary- Troy

That's all for the topic Java Stream Collectors.teeing() Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

September 15, 2021

Java Stream Collectors.partitioningBy() Examples

In this tutorial we’ll see examples of Collectors.partitioningBy() method which is part of the Collectors class in the Java Stream API.

Collectors.partitioningBy() method partitions the input elements according to a passed Predicate (which defines the condition for partitioning), and organizes them into a Map<Boolean, List> with values assigned to two keys "false" and "true" based on whether the input element passes the condition or not.

There are two overloaded Collectors.partitioningBy() methods-

1. Collector<T,?,Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate)- Partitions the input elements according to the passed Predicate.

2. Collector<T,?,Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T,A,D> downstream)- In this method along with a Predicate another Collector is also passed as an argument that reduces the values in each partition, and organizes them into a Map<Boolean, D> whose values are the result of the downstream reduction.

Collectors.partitioningBy() Java examples

1. In this simple example we’ll use the partitioningBy() method to partition the list of integers into a map of even and odd numbers.

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class PartitioningDemo {

  public static void main(String[] args) {
    List<Integer> listOfNumbers = Arrays.asList(10, 25, 9, 87, 56, 2, 31);
    Map<Boolean, List<Integer>> numbers = listOfNumbers.stream()
                               .collect(Collectors.partitioningBy(n -> n%2 == 0));
    // false key - returns list with odd numbers
    System.out.println("Odd Numbers- " + numbers.get(false));
    // true key - returns list with even numbers
    System.out.println("Even Numbers- " + numbers.get(true));
  }
}
Output
Odd Numbers- [25, 9, 87, 31]
Even Numbers- [10, 56, 2]

In the example n -> n%2 == 0 is an implementation of the Predicate functional interface using lambda expression.

2. Partition a list of Students into those who are studying science and those who are not.

Student class used is as given below
public class Student {
  private int rollNo;
  private String name;
  private String stream;
  private int marks;
  Student(int rollNo, String name, String stream, int marks){
    this.rollNo = rollNo;
    this.name = name;
    this.stream = stream;
    this.marks = marks;
  }
  public int getRollNo() {
    return rollNo;
  }
  public void setRollNo(int rollNo) {
    this.rollNo = rollNo;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getStream() {
    return stream;
  }
  public void setStream(String stream) {
    this.stream = stream;
  }
  public int getMarks() {
    return marks;
  }
  public void setMarks(int marks) {
    this.marks = marks;
  }
  @Override
  public String toString() {
    return "Roll Number: " +  getRollNo() + " Name: " + getName();
  }
}
public class PartitioningDemo {

  public static void main(String[] args) {
      List<Student> studentList = Arrays.asList(new Student(1, "Peter", "Science", 75),
              new Student(2, "Ram", "Science", 99),
              new Student(3, "Priscilla", "Art", 68),
              new Student(4, "Mahesh", "Art", 62),
              new Student(5, "Scott", "Commerce", 72));
    // List with resulting elements
    Map<Boolean, List<Student>> numbers = studentList.stream()
                             .collect(Collectors.partitioningBy(s -> s.getStream().equals("Science")));

    System.out.println("In Science stream- " + numbers.get(true));
    System.out.println("Not in Science stream- " + numbers.get(false));
  }
}
Output
In Science stream- [Roll Number: 1 Name: Peter, Roll Number: 2 Name: Ram]
Not in Science stream- [Roll Number: 3 Name: Priscilla, Roll Number: 4 Name: Mahesh, Roll Number: 5 Name: Scott]

3. Using the partitioningBy() method with two arguments. If you want to get the count of students studying science and those who are not then you can pass Collectors.counting() as the second argument.

public class PartitioningDemo {

  public static void main(String[] args) {
      List<Student> studentList = Arrays.asList(new Student(1, "Peter", "Science", 75),
              new Student(2, "Ram", "Science", 99),
              new Student(3, "Priscilla", "Art", 68),
              new Student(4, "Mahesh", "Art", 62),
              new Student(5, "Scott", "Commerce", 72));

    Map<Boolean, Long> numbers = studentList.stream()
                        .collect(Collectors.partitioningBy(s -> s.getStream().equals("Science"),
                             Collectors.counting()));

    System.out.println("Count of students in Science stream- " + numbers.get(true));
    System.out.println("Count of students not in Science stream- " + numbers.get(false));
  }
}
Output
Count of students in Science stream- 2
Count of students not in Science stream- 3

That's all for the topic Java Stream Collectors.partitioningBy() Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

September 14, 2021

Java Stream Collectors.groupingBy() Examples

In this tutorial we’ll see some examples of Collectors.groupingBy() method in Java Stream API. Collectors.groupingBy() method works similarly to "group by" statement in SQL which groups the elements as per the specified columns. This method also groups the elements according to the passed property and returns grouped result as a Map.

There are three overloaded Collectors.groupingBy() method-

  • Collector<T,?,Map<K,List>> groupingBy(Function<? super T,? extends K> classifier)- This method groups elements according to a classification function and returns the results in a Map. The collector produces a Map<K, List> where key specifies a group and List contains the elements which map to the associated key
  • Collector<T,?,Map<K,D>> groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)- In this groupingBy() method first elements are grouped according to a passed classification function and then reduction operation is performed on the values associated with a given key using the Collector passed as second argument.
  • Collector<T,?,M> groupingBy(Function<? super T,? extends K> classifier, Supplier mapFactory, Collector<? super T,A,D> downstream)- In this variant first elements are grouped according to a passed classification function then reduction operation is performed on the values associated with a given key using the Collector passed as second argument. The resulting Map produced by the Collector is created with the supplied factory function.

Note that the returned Collector, in all the above methods, is not concurrent. There is a groupingByConcurrent() method with the same 3 overloaded methods which may offer better parallel performance. In case of groupingByConcurrent() methods a ConcurrentMap is returned.

Collectors.groupingBy() Java examples

For the example we’ll use the objects of the Student class.

public class Student {
  private int rollNo;
  private String name;
  private String stream;
  private int marks;
  Student(int rollNo, String name, String stream, int marks){
    this.rollNo = rollNo;
    this.name = name;
    this.stream = stream;
    this.marks = marks;
  }
  public int getRollNo() {
    return rollNo;
  }
  public void setRollNo(int rollNo) {
    this.rollNo = rollNo;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getStream() {
    return stream;
  }
  public void setStream(String stream) {
    this.stream = stream;
  }
  public int getMarks() {
    return marks;
  }
  public void setMarks(int marks) {
    this.marks = marks;
  }
}

1. If we need to group student according to the subject stream we can use the first Collectors.groupingBy() method where only a single argument classifier function is passed.

public class GroupingDemo {

  public static void main(String[] args) {
    List<Student> studentList = Arrays.asList(new Student(1, "Peter", "Science", 75),
            new Student(2, "Ram", "Science", 99),
            new Student(3, "Priscilla", "Art", 68),
            new Student(4, "Mahesh", "Art", 62),
            new Student(5, "Scott", "Commerce", 72));
    Map<String, List<Student>> names = studentList.stream()
        .collect(Collectors.groupingBy(Student::getStream));
    // Iterating the returned Map
    names.entrySet().forEach(es->{System.out.println("Stream- " + es.getKey());
    System.out.println("**Students**");
    es.getValue().forEach(e->System.out.println(e.getName()));});

  }
}
Output
Stream- Art
**Students**
Priscilla
Mahesh
Stream- Science
**Students**
Peter
Ram
Stream- Commerce
**Students**
Scott

2. If you want the count of students in each stream then you need to group student according to the subject stream and also pass Collectors.counting() (which returns a collector) as a second argument.

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class GroupingDemo {

  public static void main(String[] args) {
    List<Student> studentList = Arrays.asList(new Student(1, "Peter", "Science", 75),
            new Student(2, "Ram", "Science", 99),
            new Student(3, "Priscilla", "Art", 68),
            new Student(4, "Mahesh", "Art", 62),
            new Student(5, "Scott", "Commerce", 72));
    Map<String, Long> names = studentList.stream()
        .collect(Collectors.groupingBy(Student::getStream, Collectors.counting()));

    names.entrySet().forEach(es-> {
              System.out.println("Stream- " + es.getKey() + 
                  " Number of Students- " + es.getValue());
              });
  }
}
Output
Stream- Art Number of Students- 2
Stream- Science Number of Students- 2
Stream- Commerce Number of Students- 1

3. If you want the Max marks in each stream then you need to group student according to the stream and also pass Collectors.maxBy (which returns a collector) as a second argument.

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class GroupingDemo {

  public static void main(String[] args) {
    List<Student> studentList = Arrays.asList(new Student(1, "Peter", "Science", 75),
            new Student(2, "Ram", "Science", 99),
            new Student(3, "Priscilla", "Art", 68),
            new Student(4, "Mahesh", "Art", 62),
            new Student(5, "Scott", "Commerce", 72));
    Map<String, Optional<Student>> names = studentList.stream()
        .collect(Collectors.groupingBy(Student::getStream, Collectors.maxBy(Comparator.comparingInt(Student::getMarks))));

    names.entrySet().forEach(es-> {
              System.out.println("Stream- " + es.getKey() + 
                  " Student Name- " + es.getValue().get().getName() +
                  " Marks- " + es.getValue().get().getMarks());
              });
  }
}
Output
Stream- Art Student Name- Priscilla Marks- 68
Stream- Science Student Name- Ram Marks- 99
Stream- Commerce Student Name- Scott Marks- 72

4. If you want sorting to be done by keys you can return a TreeMap as a result of using groupingBy() method. In that case you have to pass one more argument which is of type Supplier and acts as a factory function.

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class GroupingDemo {

  public static void main(String[] args) {
    List<Student> studentList = Arrays.asList(new Student(1, "Peter", "Science", 75),
            new Student(2, "Ram", "Science", 99),
            new Student(3, "Priscilla", "Art", 68),
            new Student(4, "Mahesh", "Art", 62),
            new Student(5, "Scott", "Commerce", 72));
    Map<String, Set<String>> names = studentList.stream()
        .collect(Collectors.groupingBy(Student::getStream, TreeMap::new, Collectors.mapping(Student::getName, Collectors.toSet())));

    names.entrySet().forEach(es-> {
              System.out.println("Stream- " + es.getKey());
              System.out.println("**Students**");
                es.getValue().forEach(name -> System.out.println(name));
                });
  }
}
Output
Stream- Art
**Students**
Priscilla
Mahesh
Stream- Commerce
**Students**
Scott
Stream- Science
**Students**
Peter
Ram

Collectors.groupingByConcurrent() Java example

1. If you need to group student according to the stream in parallel we can use the Collectors.groupingByConcurrent() method where only a single argument classifier function is passed.

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

public class GroupingDemo {

    public static void main(String[] args) {
        List<Student> studentList = Arrays.asList(new Student(1, "Peter", "Science", 75),
                new Student(2, "Ram", "Science", 99),
                new Student(3, "Priscilla", "Art", 68),
                new Student(4, "Mahesh", "Art", 62),
                new Student(5, "Scott", "Commerce", 72));
        ConcurrentMap<String, List<Student>> names = studentList.parallelStream()
            .collect(Collectors.groupingByConcurrent(Student::getStream));
        // Iterating the returned Map
        names.entrySet().forEach(es->{System.out.println("Stream- " + es.getKey());
        System.out.println("**Students**");
        es.getValue().forEach(e->System.out.println(e.getName()));});

    }
}

That's all for the topic Java Stream Collectors.groupingBy() Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

September 13, 2021

Java Stream Collectors.joining() Method With Examples

In this tutorial we’ll see how to use Collectors.joining() method to concatenate the input elements into a String. It is a handy utility method provided by the Collectors class in Java Stream API to quickly convert array elements or elements in a collection to String.

There are three overloaded Collectors.joining() method-

  • Collector<CharSequence,?,String> joining()- Concatenates the input elements into a String, in encounter order.
  • Collector<CharSequence,?,String> joining(CharSequence delimiter)- In this method you can also pass a delimiter, it concatenates the input elements, separated by the specified delimiter, in encounter order.
  • Collector<CharSequence,?,String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)- This method concatenates the input elements, separated by the specified delimiter, with the specified prefix and suffix, in encounter order.

Collectors.joining() Java Stream API examples

1. In this example we’ll pass a character array as a stream to the collect method where Collectors.joining() method is used to get a single string concatenating all the characters of the character array.

import java.util.stream.Collectors;
import java.util.stream.Stream;

public class JoiningDemo {

  public static void main(String[] args) {
    char[] ch = { 'T', 'h', 'i', 's', ' ',
                  'i', 's', ' ',
                  'S', 't', 'r', 'i', 'n', 'g' };
    String str1 = Stream.of(ch).map(c->new String(c)).collect(Collectors.joining());
    System.out.println("Concatenated String- " + str1);
  }
}
Output
Concatenated String- This is String

2. In this example we’ll pass an array of String as a Stream to the collect method to get a single string. We’ll also use the joining method where delimiter is passed as an argument.

public class JoiningDemo {

  public static void main(String[] args) {
    String[] strArr = { "This", "is", "a", "String" };
    String str1 = Stream.of(strArr).collect(Collectors.joining());
    System.out.println("Concatenated String- " + str1);
    
    // Passing Space as delimiter
    String str2 = Stream.of(strArr).collect(Collectors.joining(" "));
    System.out.println("Concatenated String with delimiter- " + str2);
    // Passing pipe as delimiter
    str2 = Stream.of(strArr).collect(Collectors.joining("|"));
    System.out.println("Concatenated String with delimiter- " + str2);
    
    // Passing delimiter, suffix and prefix
    String str3 = Stream.of(strArr).collect(Collectors.joining("|", "[", "]"));
    System.out.println("Concatenated String with delimiter and suffix, prefix- " + str3);
  }
}
Output
Concatenated String- ThisisaString
Concatenated String with delimiter- This is a String
Concatenated String with delimiter- This|is|a|String
Concatenated String with delimiter and suffix, prefix- [This|is|a|String]

3. In this example we’ll join the elements of an ArrayList using Collectors.joining() method of the Java Stream API.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class JoiningDemo {

  public static void main(String[] args) {
    List<String> cityList = Arrays.asList("Delhi", "Mumbai", "London", "New York","Bengaluru");
    String str1 = cityList.stream().collect(Collectors.joining());
    System.out.println("Concatenated String- " + str1);
    
    // Passing Space as delimiter
    String str2 = cityList.stream().collect(Collectors.joining(" "));
    System.out.println("Concatenated String with delimiter- " + str2);
    // Passing pipe as delimiter
    str2 = cityList.stream().collect(Collectors.joining("|"));
    System.out.println("Concatenated String with delimiter- " + str2);
    
    // Passing delimiter, suffix and prefix
    String str3 = cityList.stream().collect(Collectors.joining("|", "[", "]"));
    System.out.println("Concatenated String with delimiter and suffix, prefix- " + str3);
  }
}
Output
Concatenated String- DelhiMumbaiLondonNew YorkBengaluru
Concatenated String with delimiter- Delhi Mumbai London New York Bengaluru
Concatenated String with delimiter- Delhi|Mumbai|London|New York|Bengaluru
Concatenated String with delimiter and suffix, prefix- [Delhi|Mumbai|London|New York|Bengaluru]

That's all for the topic Java Stream Collectors.joining() Method With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

July 16, 2021

Spring Boot Microservices Eureka + Ribbon

In the article Spring Boot Microservices example we saw an example of Spring Boot Microservice where we used Eureka for service registration and discovery. In this post we’ll extend that example further to see how to use both Eureka and Ribbon load balancer with Spring Boot to load the balance among micro services instances.

In the example there were two separate services User and Account which were registered with Eureka. From User service there was a call to Account service to fetch some data. In this post we’ll see how to configure more than one instances of Account service and use Ribbon load balancer to route the calls among those instances of Account service.

Load Balancing

In simple terms load balancing means distributing the load across several resources rather than putting all the load on a single resource. That helps in increasing throughput as there are more resources to share the load, increases reliability as there are more redundant resource to process the request even if any one resource goes down so there is no single point of failure.

Using Ribbon load balancing with Eureka

As we know Eureka is used for service registration and discovery where as Ribbon is a client side load balancer. Here I’ll try to explain how these two tools work together.

Eureka server maintains a service registry by registering each microservice with the Eureka server. When the inter-service communication happens, calling service interrogates the service registry using DiscoveryClient and gets in return all the instances of the called microservice. Now the question is, out of all the returned instances which one to call?

That’s where client load balancer like Ribbon comes in the picture. Client side load balancer uses an algorithm, like round robin (calling each instance in sequence) or using zone information (to locate a server in the same zone as the client), to get the instance of service that has to be called.

Spring Boot Microservice with loadbalancing

Spring Boot Micro Service with Ribbon example

In the Spring Boot Microservices example we have already seen how to configure Eureka server and how to register micro services User and Account with it by enabling discovery client (Using @EnableDiscoveryClient (or @EnableEurekaClient) annotation).

One problem was the URL used while calling Account service from User which hardcodes the host and port. That means every time same service will be called even if you create more instances of Account.

List<Account> accounts = new RestTemplate().exchange(
       "http://localhost:9000/accounts/{empId}", HttpMethod.GET, null, new
       ParameterizedTypeReference<List<Account>>(){}, id).getBody();

What we want is an abstraction so that the correct host and port is resolved at run time, that’s what we’ll try to configure using Netflix’s Ribbon load balancing service. To enable Ribbon you need to add the following dependency in pom.xml

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

Though this dependency is added automatically when you add dependency for eureka client. So, this dependency will suffice.

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Creating another instance of Account Microservice

Now we want to scale up our service and need multiple copies of Account. We already created a Spring Boot project for the same with the following in application.properties.

eureka.client.service-url.default-zone=http://localhost:8761/eureka
server.port=9000
spring.application.name=account

After starting this instance you can change the port to 9001 and start the Account application again. That way you will have two instances one listening at port 9000 and another at port 9001.

You can also create a Separate Spring Boot project copy the files from Account and paste the following in application.properties

eureka.client.service-url.default-zone=http://localhost:8761/eureka
server.port=9001
spring.application.name=account

Either way you will have two instances running of the same service. You can verify it in Eureka Server by accessing URL - http://localhost:8761/

Eureka with ribbon

Creating LoadBalanced aware RestTemplate

Since we are using an instance of RestTemplate to make a call to another Service so we can make the RestTemplate bean load balanced aware. That can be done using @LoadBalanced annotation which instructs Netflix Ribbon to wrap this RestTemplate bean with load balancing advice.

Create a Config.java class in SpringBootUser project as given below.

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
 
@Configuration 
public class Config { 
  @Bean 
  @LoadBalanced
  RestTemplate restTemplate() { 
    return new RestTemplate(); 
  } 
}

Injecting the load balanced RestTemplate

Now you can inject this load balanced RestTemplate into the UserService. Then, in the URL, you can use the logical name of the service that was used to register it with Eureka. That’s how we are using this URL http://ACCOUNT/accounts/{empId} to access Account MicroService instance.

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.client.RestTemplate;

@Service
public class UserService {
  @Autowired
  private RestTemplate restTemplate;
    public List<Account> showEmployees(@PathVariable("id") String id) {
    System.out.println(id);
    
    List<Account> accounts = restTemplate.exchange(
    "http://ACCOUNT/accounts/{empId}", HttpMethod.GET, null, new
    ParameterizedTypeReference<List<Account>>(){}, id).getBody();
    // Another call (to demo load balancing)
    accounts = restTemplate.exchange(
            "http://ACCOUNT/accounts/{empId}", HttpMethod.GET, null, new
            ParameterizedTypeReference<List<Account>>(){}, id).getBody();
    return accounts;          
  }
}

Testing the application

Once the changes are done start all the applications i.e. Eureka Server, User MicroService and two instances of Account MicroService.

In the console of SpringBootUser application you can see that the Load balancer has recognized the list of servers and one of them will be called to service the request.

2020-04-25 17:02:06.405  INFO 9908 --- [nio-8080-exec-1] c.netflix.config.ChainedDynamicProperty  : Flipping property: ACCOUNT.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2020-04-25 17:02:06.573  INFO 9908 --- [nio-8080-exec-1] c.n.u.concurrent.ShutdownEnabledTimer    : Shutdown hook installed for: NFLoadBalancer-PingTimer-ACCOUNT
2020-04-25 17:02:06.575  INFO 9908 --- [nio-8080-exec-1] c.netflix.loadbalancer.BaseLoadBalancer  : Client: ACCOUNT instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=ACCOUNT,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2020-04-25 17:02:06.637  INFO 9908 --- [nio-8080-exec-1] c.n.l.DynamicServerListLoadBalancer      : Using serverListUpdater PollingServerListUpdater
2020-04-25 17:02:06.905  INFO 9908 --- [nio-8080-exec-1] c.netflix.config.ChainedDynamicProperty  : Flipping property: ACCOUNT.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2020-04-25 17:02:06.923  INFO 9908 --- [nio-8080-exec-1] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client ACCOUNT initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=ACCOUNT,current list of Servers=[user:9000, user:9001],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;	Instance count:2;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
},Server stats: [[Server:user:9000;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 05:30:00 IST 1970;	First connection made: Thu Jan 01 05:30:00 IST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
, [Server:user:9001;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 05:30:00 IST 1970;	First connection made: Thu Jan 01 05:30:00 IST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@7ef76128

Accessing services

You can start the service by accessing URL- http://localhost:8080/user/1

Try to access the same URL in different browser tabs and you can see that requests are getting divided between two Account service instances.

Download source code- https://github.com/knpcode/SpringBoot-MicroService-Ribbon

That's all for the topic Spring Boot Microservices Eureka + Ribbon. If something is missing or you have something to share about the topic please write a comment.


You may also like

July 15, 2021

Spring Boot Microservices Example

In this article we’ll see a Spring Boot Microservices example with Eureka used for service registration and discovering the service. We’ll have two separate services User and Account developed as Microservices. With two microservices we'll also see how to call one microservice from another using RestTemplate.

When a large monolith application is split into two or more microservices those microservices may need to interact with each other. To do that these microservices need to be aware of each others existence and should be able to find each other. This process is known as service discovery. There is a tool called Eureka created by Netflix that can act as a discovery server, for that you need to register your microservices with the Eureka server.

So, in this Spring Boot Microservices example we are going to create 3 separate Spring Boot applications two for the functionality of User and Account and third one for Eureka Server.

Spring Boot application for Eureka Server

First we’ll create a Spring Boot project for configuring Eureka Server, this application acts as a service registry.

Starter you need to add for Eureka Server is spring-cloud-starter-netflix-eureka-server

Maven dependencies – pom.xml

pom.xml with the starter dependencies. Note that Spring Boot version used is 2.3.4.RELEASE and Spring Cloud version is Hoxton.SR8

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
    <relativePath/>
  </parent>
  <groupId>com.knpcode</groupId>
  <artifactId>springeureka</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>EurekaServer</name>
  <description>Eureka Server project</description>

  <properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

Application class

Application class with main method.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
  public static void main(String[] args) {
    SpringApplication.run(EurekaServerApplication.class, args);
  }
}

In the application class apart from @SpringBootApplication annotation another annotation @EnableEurekaServer is also added.

@EnableEurekaServer annotation indicates that we want to run a Eureka Server. By seeing a dependency on Spring Cloud Eureaka, Spring Boot automatically configures the application as a service registry.

Eureka Server Configuration

In the application.properties put the following.

server.port=8761
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

server.port configures the port Eureka Server runs on.

For a stand alone instance we don’t want Eureka Server to be a client too that is why these two entries-

eureka.client.register-with-eureka=false

eureka.client.fetch-registry=false

Running Eureka Server

Eureka server application is ready and you can run the EurekaServerApplication class to start the Eureka Server.

You should get the following messages if every thing runs fine-

2020-03-12 14:53:16.457  INFO 14400 --- [      Thread-10] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
2020-03-12 14:53:16.503  INFO 14400 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8761 (http) with context path ''
2020-03-12 14:53:16.507  INFO 14400 --- [           main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8761
2020-03-12 14:53:19.314  INFO 14400 --- [           main] o.n.s.EurekaServerApplication            : Started EurekaServerApplication in 30.203 seconds (JVM running for 33.929)

You can see the Eureka Server console by accessing URL- http://localhost:8761/

Spring boot eureka server

As you can see currently no instances are registered with Eureka. That’s what is the next task, to create Spring Boot microservices and register them with Eureka Server.

Spring Boot Account application

Create another Spring Boot project for Account Microservice, starter dependency for eureka client has to be added, in place of eureka-server, to register this Microservice as Eureka client. All the other dependencies remain same as used in Eureka Server application.

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Rest Controller class

We’ll add a controller with the functionality to find all the accounts for the passed EmployeeId.

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AccountController {
  @Autowired
  private AccountService accountService;
  
  @GetMapping(value="/accounts/{empId}") 
  public List<Account>getAccountsByEmpId(@PathVariable String empId) { 
    System.out.println("EmpId------" + empId);
    List<Account> empAccountList = accountService.findAccountsByEmpId(empId);
    return empAccountList; 
  }
}

AccountService class

In the AccountService class you can see there is a dummy method to get the Accounts rather than accessing DB in order to keep the focus on interaction between microservices.

import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;

@Service
public class AccountService {
  public List<Account> findAccountsByEmpId(String empId){
    List<Account> accountList = getAccountList();
    List<Account> empAccountList = new ArrayList<>();
    for(Account account :  accountList) {
      if(account.getEmpId().equals(empId))
        empAccountList.add(account);
    }
    return empAccountList;
  }
    
  private List<Account> getAccountList(){
    List<Account> accountList = new ArrayList<>();
    accountList.add(new Account("1", "AC1", "MT"));
    accountList.add(new Account("1", "AC2", "IN"));
    accountList.add(new Account("2", "AC3", "IN"));
    return accountList;
  }
}
DTO Class

There is also an Account class that acts as a DTO or a model bean.

public class Account {
  private String empId;
  private String accountId;
  private String branch;
  Account(){
    
  }
  Account(String empId, String accountId, String branch){
    this.empId = empId;
    this.accountId = accountId;
    this.branch = branch;
  }
  public String getEmpId() {
    return empId;
  }
  public void setEmpId(String empId) {
    this.empId = empId;
  }
  public String getAccountId() {
    return accountId;
  }
  public void setAccountId(String accountId) {
    this.accountId = accountId;
  }
  public String getBranch() {
    return branch;
  }
  public void setBranch(String branch) {
    this.branch = branch;
  }
}

Application class

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class SpringBootAccountApplication {
  public static void main(String[] args) {
    SpringApplication.run(SpringBootAccountApplication.class, args);
  }
}

Application class is annotated with the @EnableDiscoveryClient (so that this application can be discovered as Eureka client, you can also use @EnableEurekaClient annotation instead of @EnableDiscoveryClient) along with the @SpringBootApplication annotation.

Configuration for Eureka Client

Following properties are also to be added to the application.properties file to register Account Microservice as Eureka client.

eureka.client.service-url.default-zone=http://localhost:8761/eureka
server.port=9000
spring.application.name=account

eureka.client.service-url.default-zone property tells our microservice where to look for Eureka Server.

Using spring.application.name you give a logical name to your microservice.

Server port is configured as 9000 so this Account application runs on port 9000.

Registering Account Microservice as Eureka client

Run the SpringBootAccountApplication class to start this RESTful service. It will automatically be registered as Eureka client. You can verify that seeing the messages on the console.

2020-03-12 15:23:58.585  INFO 12416 --- [  restartedMain] o.s.c.n.e.s.EurekaServiceRegistry        : Registering application ACCOUNT with eureka with status UP
2020-03-12 15:23:58.588  INFO 12416 --- [  restartedMain] com.netflix.discovery.DiscoveryClient    : Saw local status change event StatusChangeEvent [timestamp=1584006838588, current=UP, previous=STARTING]
2020-03-12 15:23:58.597  INFO 12416 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_ACCOUNT/user:account:9000: registering service...
2020-03-12 15:23:58.940  INFO 12416 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 9000 (http) with context path ''
2020-03-12 15:23:58.945  INFO 12416 --- [  restartedMain] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 9000
2020-03-12 15:23:59.194  INFO 12416 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_ACCOUNT/user:account:9000 - registration status: 204
2020-03-12 15:24:02.128  INFO 12416 --- [  restartedMain] o.n.a.SpringBootAccountApplication       : Started SpringBootAccountApplication in 31.85 seconds (JVM running for 35.175)

Verifying Eureka Server

If you refresh the URL for Eureka Server- http://localhost:8761/ now you should see an instance registered. Name of the instance is same as what was configured as a logical name using the following property.

spring.application.name=account

Spring Boot microservice example

Spring Boot User application

Another microservice we need to create is the User service so create another project. There again add the same starter dependency to register this Microservice as Eureka client.

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Rest Controller class

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
  @Autowired
  private UserService userService;
  
  @GetMapping(value="/user/{id}")
  public List<Account> showEmployees(@PathVariable("id") String id) {     
    List<Account> accounts = userService.showEmployees(id);
    // displaying accounts
    for(Account acct : accounts) {
      System.out.println(acct.getEmpId());
      System.out.println(acct.getAccountId());
      System.out.println(acct.getBranch());
    }
    return accounts;          
  }
}
UserService Class

In method showEmployees there is a call to the Account microservice to get all the associated accounts for the passed employee ID.

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.client.RestTemplate;

@Service
public class UserService {
  @Autowired
  private RestTemplate restTemplate;
  public List<Account> showEmployees(@PathVariable("id") String id) {
    System.out.println(id);
    //List<Account> accounts = new RestTemplate().exchange(
    //"http://localhost:9000/accounts/{empId}", HttpMethod.GET, null, new
    //ParameterizedTypeReference<List<Account>>(){}, id).getBody();
    
    List<Account> accounts = restTemplate.exchange(
    "http://ACCOUNT/accounts/{empId}", HttpMethod.GET, null, new
    ParameterizedTypeReference<List<Account>>(){}, id).getBody();
    return accounts;          
  }
}

restTemplate.exchange() is the method used for making remote call to another microservice.

  • First argument to restTemplate.exchange() is the URL to the Account microservice- "http://ACCOUNT/accounts/{empId}"
  • Second argument specifies that it is a HTTP Get command.
  • Third argument specifies the entity (headers and/or body) to write to the request. As we are not passing any request entity so it is null.
  • Fourth argument specifies the type of the response.
  • Fifth argument specifies the variables to expand in the template. We are passing id there which will replace {empId} in the URL.

Using Ribbon Load Balancer

In the above method you can see that the URL used for calling Microservice is http://ACCOUNT/accounts/{empId} though you can also use http://localhost:9000/accounts/{empId} but that hardcodes the location which is not good.

To avoid that hardcoding we are using Netflix's Ribbon service which can be integrated with Eureka. What we need to do is to mark a RestTemplate bean to be configured to use a LoadBalancerClient, to do that we can create RestTemplate bean as following.

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration 
public class Config { 
  @Bean 
  @LoadBalanced
  RestTemplate restTemplate() { 
    return new RestTemplate(); 
  } 
}

Once you have this Load balanced restTemplate instance then you can use the logical name of the service, in the URL, that was used to register it with Eureka. That’s how we are using this URL http://ACCOUNT/accounts/{empId} to access Account MicroService.

Application class

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class SpringBootUserApplication {
  public static void main(String[] args) {
    SpringApplication.run(SpringBootUserApplication.class, args);
  }
}

Configuration for Eureka Client

Following properties are also to be added to the application.properties file to register User Microservice as Eureka client.

eureka.client.service-url.default-zone=http://localhost:8761/eureka
spring.application.name=user

Run the SpringBootUserApplication to start User MicroService. It will automatically be registered as Eureka client. You can verify that by seeing the messages on the console.

2020-03-12 16:24:00.228  INFO 9844 --- [  restartedMain] o.s.c.n.e.s.EurekaServiceRegistry        : Registering application USER with eureka with status UP
2020-03-12 16:24:00.231  INFO 9844 --- [  restartedMain] com.netflix.discovery.DiscoveryClient    : Saw local status change event StatusChangeEvent [timestamp=1584010440231, current=UP, previous=STARTING]
2020-03-12 16:24:00.240  INFO 9844 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_USER/user:user: registering service...
2020-03-12 16:24:00.402  INFO 9844 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_USER/user:user - registration status: 204
2020-03-12 16:24:00.572  INFO 9844 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-03-12 16:24:00.577  INFO 9844 --- [  restartedMain] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8080
2020-03-12 16:24:03.278  INFO 9844 --- [  restartedMain] com.knpcode.user.SpringBootUserApplication   : Started SpringBootUserApplication in 28.889 seconds (JVM running for 33.647)

If you refresh the URL for Eureka Server- http://localhost:8761/ you should see both the MicroServices registered as Eureka clients.

Microservice with load balancing

Communication between MicroServices

Now we have two MicroSerivces created and running. Both of the MicroServices are registered with Eureka so these services can be discovered using Eureka.

Now when you access the URL http://localhost:8080/user/1 it will be serviced by showEmployees() method of the UserController in SpringBootUser application. From there using restTemplate.exchange() method it communicates with Account service.

Microservice with eureka client discovery

The URL (http://ACCOUNT/accounts/{empId}) passed in the exchange method triggers the whole process of load balancing and discovering the Eureka client as evident from the log messages.

2020-03-12 16:36:37.733  INFO 9844 --- [nio-8080-exec-1] c.netflix.config.ChainedDynamicProperty  : Flipping property: ACCOUNT.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2020-03-12 16:36:37.915  INFO 9844 --- [nio-8080-exec-1] c.n.u.concurrent.ShutdownEnabledTimer    : Shutdown hook installed for: NFLoadBalancer-PingTimer-ACCOUNT
2020-03-12 16:36:37.916  INFO 9844 --- [nio-8080-exec-1] c.netflix.loadbalancer.BaseLoadBalancer  : Client: ACCOUNT instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=ACCOUNT,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2020-03-12 16:36:37.963  INFO 9844 --- [nio-8080-exec-1] c.n.l.DynamicServerListLoadBalancer      : Using serverListUpdater PollingServerListUpdater
2020-03-12 16:36:38.090  INFO 9844 --- [nio-8080-exec-1] c.netflix.config.ChainedDynamicProperty  : Flipping property: ACCOUNT.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2020-03-12 16:36:38.098  INFO 9844 --- [nio-8080-exec-1] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client ACCOUNT initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=ACCOUNT,current list of Servers=[user:9000],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;	Instance count:1;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
},Server stats: [[Server:user:9000;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 05:30:00 IST 1970;	First connection made: Thu Jan 01 05:30:00 IST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@5820f552
1
AC1
MT
1
AC2
IN
2020-03-12 16:36:38.995  INFO 9844 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: ACCOUNT.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
favicon.ico
2020-03-12 16:38:59.147  INFO 9844 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2020-03-12 16:43:59.150  INFO 9844 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration

Download sourcecode- SpringBoot-MicroService-Example

That's all for the topic Spring Boot Microservices Example. If something is missing or you have something to share about the topic please write a comment.


You may also like

July 10, 2021

How to Copy a Directory in Java

This post shows how to copy a directory in Java where all the files and sub folders with in a directory are recursively copied to a new directory.

Options for copying a directory in Java

For copying the folder tree structure which includes the sub-directories and all the files you can use one of the following options in Java-

  • Using File.listFiles() method which returns an array of abstract pathnames denoting the files in the directory. Then you can iterate the array to list the files and copy them to the target directory, you will have to recursively call your method to list files with in the sub-directories. See example.
  • Java 7 onward you can use Files.walkFileTree method which walks a file tree rooted at a given starting file. See example.
  • Java 8 onward you can use Files.walk() method which returns the Path objects as stream by walking the file tree rooted at a given starting file. See example.

Directory structure used

Java programs shown here to copy a directory in Java use the following directory structure.

copy a folder in Java

With in the parent folder there is one sub folder Child with two files and one file is stored in the parent folder.

Copying directory in Java using Files.walk() method

Java 8 onward You can use Files.walk() method which returns the Path objects as stream. Each path in that Stream can be checked then to verify whether it's a directory or a file. If it is a file, it has to be copied to the target location, in case of directory you need to create that directory at the target location.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class CopyDirectory {
  public static void main(String[] args) {
    final String SOURCE_DIR = "F:/knpcode/Parent";
    final String TARGET_DIR = "F:/knpcode/Parent_New";
    directoryCopy(SOURCE_DIR, TARGET_DIR);
  }

  private static void directoryCopy(String sourceDir, String targetDir){
    Path sourcePath = Paths.get(sourceDir);
    Path targetPath = Paths.get(targetDir);
    try(Stream<Path> filePaths = Files.walk(sourcePath)) {
      filePaths.forEach(filePath -> {
        try {
          if (Files.isRegularFile(filePath)) {
            Path newFile = targetPath.resolve(sourcePath.relativize(filePath));
            Files.copy(filePath, newFile);
            System.out.println("Copied file " + newFile);
          }else{
            Path newDir = targetPath.resolve(sourcePath.relativize(filePath));
            Files.createDirectory(newDir);
            System.out.println("Created Directory " + newDir);
          }
        }catch (IOException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        } 
      });
    }catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
Output
Created Directory F:\knpcode\Parent_New
Created Directory F:\knpcode\Parent_New\Child
Copied file F:\knpcode\Parent_New\Child\hello.txt
Copied file F:\knpcode\Parent_New\Child\Project.docx
Copied file F:\knpcode\Parent_New\Test.txt

Copying directory in Java using Files.walkFileTree() method

Java 7 onward You can use Files.walkFileTree() method using which you can walk the tree structure of the source directory and copy all files and sub-directories in the process.

One of the argument of this method is a FileVisitor interface. You do need to provide implementation of this interface as per your requirement.

FileVisitor interface has four methods, for listing files in a folder you do need to implement two of them; preVisitDirectory and visitFile.

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

public class CopyDirectory {
  public static void main(String[] args) {
    final String SOURCE_DIR = "F:/knpcode/Parent";
    final String TARGET_DIR = "G:/Parent_New";
    try {
      directoryCopy(SOURCE_DIR, TARGET_DIR);
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  private static void directoryCopy(String sourceDir, String targetDir) throws IOException{
    Path sourcePath = Paths.get(sourceDir);
    Path targetPath = Paths.get(targetDir);
    // Walk the tree structure using WalkFileTree method       
    Files.walkFileTree(sourcePath, new SimpleFileVisitor<Path>(){
      @Override
      // Before visiting the directory, create directory 
      public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
        Path newDir = targetPath.resolve(sourcePath.relativize(dir));
        System.out.println("Path- " + newDir.toString());
        Files.createDirectory(newDir);
        return FileVisitResult.CONTINUE;
      }
      @Override
      // For each visited file copy it
      public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
        Path newFile = targetPath.resolve(sourcePath.relativize(file));
        System.out.println("Path- " + newFile.getFileName());
        Files.copy(file, newFile);                
        return FileVisitResult.CONTINUE;
      }
    });  
  }
}

Copying directory in Java using File.listFiles() method

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class CopyDirectory {
  public static void main(String[] args) {
    final String SOURCE_PATH = "F:/knpcode/Parent";
    final String TARGET_PATH = "F:/knpcode/Parent_New";
    File sourceDir = new File(SOURCE_PATH);
    File targetDir = new File(TARGET_PATH);
    try {
      directoryCopy(sourceDir, targetDir);
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  private static void directoryCopy(File sourceDir, File targetDir) throws IOException{
    if(sourceDir.isDirectory()){
      // create target directory
      if(!targetDir.exists()){
        targetDir.mkdir();
        System.out.println("Created Directory " + targetDir);
      }
      File[] fileList = sourceDir.listFiles();
      for(File file : fileList){
        File sourceFile =  new File(sourceDir, file.getName());
        File targetFile = new File(targetDir, file.getName());
        // Recursive call in case of directory
        directoryCopy(sourceFile, targetFile);
      }
    }else{ // if it is a file
      Files.copy(sourceDir.toPath(), targetDir.toPath());
      System.out.println("Copied file " + targetDir);
    }
  }
}
Output
Created Directory F:\knpcode\Parent_New
Created Directory F:\knpcode\Parent_New\Child
Copied file F:\knpcode\Parent_New\Child\hello.txt
Copied file F:\knpcode\Parent_New\Child\Project.docx
Copied file F:\knpcode\Parent_New\Test.txt

That's all for the topic How to Copy a Directory in Java. If something is missing or you have something to share about the topic please write a comment.


You may also like