Java streams best practices

In this short post I am going to present Java 8 streams best practices. Most of them either I figured out myself or learned from my colleagues.

Let’s start with some “obvious” things about code formatting:

  • You should have at most one stream method call per line. This will make stream operations like map, filter and collect easily recognizable.
// BAD CODE: -> s.length() > 2).sorted()
	.map(s -> s.substring(0, 2)).collect(Collectors.toList());

	.filter(s -> s.length() > 2)
	.map(s -> s.substring(0, 2))
  • You should import static all of the standard stream related methods. This will make code shorter, easier to read and easier to understand by removing all unnecessary visual noise.
	.collect(Collectors.toMap(Function.identity(), String::length));

	.collect(toMap(identity(), String::length));
  • You should prefer method references to lambdas.
	.map(s -> s.length())


Method references are easier to read since we avoid all the visual noise generated by -> and () operators. They are also handled more efficiently by current version of Java. Lambda expressions like s -> s.length() are compiled to a private static method and an invokedynamic instruction.

// s -> s.lenght() is translated into:
private static Integer lambda$main$0(String s) {
	return s.length();

Method references are compiled to only invokedynamic instruction.

  • You should use methods from Class<T> to filter stream elements by a type and to cast stream elements to a type.
Stream<Object> objects = Stream.of(
	"a string",
	new String[] { "an array" },
	"another string");

List<String> strings = objects

Also rember that Class<T>::isInstance only checks if the value can be assigned to a variable of type T. For example Object.class.isInstance("foo") returns true because string "foo" can be assigned to a variable of type Object. If you want to check that stream elements have exactly type T you must use expression:

.filter(x -> (x != null) && x.getClass().equals(T.class))
  • Give meaningful names to frequently used collector expressions. In most cases this means extracting collector expression into its own method.
Map<Integer, Entity> entityById =
	.collect(toMap(Entity::getId, identity()));

Map<Integer, Entity> entityById =

private static class ExtraCollectors {
  public static Collector<Entity,?,Map<Integer,Entity>> toByIdMap() {
	return Collectors.toMap(Entity::getId, identity());

You may also consider using static import for your own frequently used collectors.

  • Use the following pattern when you sort stream values at hoc:
List<Student> result =
		.thenComparing(Student::getName, reverseOrder())
		.thenComparing(Student::getId, reverseOrder())

Notice how we used reverseOrder() to reverse order of sorting by name and id. Also bear in mind that it is always a good idea to extract complicated comparers to its own method or a final field.

  • Use IntStream, LongStream and DoubleStream when working with primitive types. They are faster (they avoid boxing) and easier to use (they add useful methods like sum).
Stream<String> strings = Stream.of("a", "foo", "bar", "baz");

double averageLength = strings

Use mapTo[Int|Long|Double] and mapToObj to convert between a stream and a specialized primitive stream.

Also learn about static helper methods exposed by specialized stream classes:

// prints: 0 1 2 3 4 5 6 7 8 9
IntStream.range(0, 10)

// prints: 1 2 4 8 16 32 64 128 256 512
IntStream.iterate(1, i -> 2*i)

ThreadLocalRandom random = ThreadLocalRandom.current();

// prints: -376368599 2112239618
// just to demo generate method:

// prints: -1134353240 2007034835
// stream of random int's - more idiomatic way:
  • Avoid using peek(). Try to make your streams free of side-effects.

This list is by no means complete. I will try to add some more practices in the future. Bye!


A Programmer, A Geek, A Human