Artículo

¿Qué es un Java Stream? Guía completa con ejemplos (2026)

Descubre qué es un Stream en Java, cómo funciona su pipeline y aprende a usarlo con ejemplos de código reales paso a paso.

Un Stream en Java es una secuencia de elementos sobre la que puedes aplicar operaciones de forma declarativa, sin escribir bucles manuales. No es una estructura de datos como una List o un Set: no almacena nada, simplemente describe un pipeline de procesamiento que se ejecuta cuando lo necesitas.

Se introdujo en Java 8 y hoy es una de las herramientas más usadas a diario por cualquier desarrollador backend, ya sea para filtrar listas, transformar datos o agregarlos.

¿Qué es un Stream en Java?

Un Stream representa un flujo de datos sobre el que aplicas una serie de operaciones encadenadas. Piensa en una cinta transportadora: los elementos entran, pasan por distintas estaciones (filtrar, transformar, ordenar) y al final obtienes un resultado.

List<String> nombres = List.of("Ana", "Luis", "Carla", "Pedro");

List<String> resultado = nombres.stream()
    .filter(nombre -> nombre.length() > 4)
    .map(String::toUpperCase)
    .sorted()
    .collect(Collectors.toList());

// resultado -> [CARLA, PEDRO]

Streams vs. Colecciones: la diferencia clave

ColecciónStream
Almacena datosNo
Se puede recorrer varias vecesNo, se consume una sola vez
Modifica la fuentePuede hacerloNunca modifica la fuente original
EjecuciónInmediataPerezosa (lazy) hasta la operación terminal

Cómo funciona un Stream: pipeline de operaciones

Un Stream siempre sigue tres fases: fuente → operaciones intermedias → operación terminal.

Operaciones intermedias (map, filter, sorted...)

Son perezosas: no se ejecutan hasta que aparece una operación terminal. Devuelven otro Stream, por lo que se pueden encadenar.

  • filter(Predicate): selecciona elementos que cumplen una condición.
  • map(Function): transforma cada elemento.
  • sorted(): ordena los elementos.
  • distinct(): elimina duplicados.
  • limit(n) / skip(n): recorta el flujo.

Operaciones terminales (collect, forEach, reduce...)

Disparan la ejecución de todo el pipeline y devuelven un resultado final (no un Stream).

  • collect(Collectors.toList()): recoge el resultado en una colección.
  • forEach(Consumer): itera sobre cada elemento.
  • reduce(...): combina todos los elementos en un único valor.
  • count(), anyMatch(), findFirst(): consultas puntuales.

Ejemplo práctico paso a paso

Crear un Stream desde una lista

List<Integer> numeros = List.of(4, 8, 15, 16, 23, 42);
Stream<Integer> stream = numeros.stream();

Encadenar operaciones (código completo)

List<Integer> pares = numeros.stream()
    .filter(n -> n % 2 == 0)
    .map(n -> n * 2)
    .sorted()
    .collect(Collectors.toList());

// pares -> [8, 16, 30, 32, 46]

Recoger el resultado con collect()

collect() es la operación terminal más usada. Además de Collectors.toList(), existen Collectors.toSet(), Collectors.toMap() y Collectors.groupingBy() para agrupar resultados por una clave.

Streams secuenciales vs. paralelos

Stream secuencialStream paralelo
EjecuciónUn solo hiloMúltiples hilos (ForkJoinPool)
Cuándo usarloColecciones pequeñas/medianasColecciones muy grandes con operaciones costosas
RiesgoNinguno especialProblemas si hay estado compartido mutable
Cómo activarlo.stream().parallelStream()

Como regla general: no uses parallelStream() por defecto. Solo aporta beneficio real con volúmenes grandes de datos y operaciones costosas por elemento; en el resto de casos, el overhead de gestionar hilos hace que sea más lento que un Stream secuencial.

Casos de uso reales en proyectos backend

  • Filtrar y transformar resultados de una consulta antes de devolverlos en una API REST.
  • Agrupar entidades por una propiedad (Collectors.groupingBy) para generar reportes.
  • Validar si una colección cumple una condición (anyMatch, allMatch) antes de continuar un flujo de negocio.
  • Sustituir bucles anidados por pipelines más legibles al combinar flatMap con listas de listas.

Errores comunes con Streams

  • Reutilizar un Stream ya consumido: lanza IllegalStateException. Un Stream se descarta tras su operación terminal; si necesitas repetir la operación, crea un nuevo Stream desde la fuente.
  • Modificar la colección de origen dentro de un forEach: provoca ConcurrentModificationException.
  • Abusar de parallelStream() sin medir si realmente mejora el rendimiento.
  • Olvidar que las operaciones intermedias son perezosas: si no añades una operación terminal, el pipeline nunca se ejecuta.

Preguntas frecuentes

¿Un Stream modifica la lista original? No. Las operaciones de Stream nunca alteran la colección de origen; siempre devuelven un nuevo resultado.

¿Puedo reutilizar un Stream después de recorrerlo? No. Cada Stream se consume una única vez. Necesitas generar uno nuevo llamando de nuevo a .stream() sobre la colección.

¿Cuál es la diferencia entre map y flatMap? map transforma cada elemento en otro elemento; flatMap transforma cada elemento en un Stream y luego los "aplana" todos en uno solo, útil para listas de listas.

¿Cuándo debería usar Streams en lugar de un bucle for? Cuando la operación es de transformación, filtrado o agregación de datos: mejora la legibilidad. Para lógica con múltiples efectos secundarios o rendimiento crítico, un bucle clásico puede ser más claro.

Siguiente paso para dominar Streams en Java

Si quieres profundizar en el filtrado avanzado con condiciones combinadas, el siguiente paso natural es dominar filter() junto con Predicate, así como aplicar estos conceptos dentro de un proyecto real con Spring Boot.

java streams java 8 programacion funcional

¿Quieres acelerar tu carrera como desarrollador Java?

Reserva una Sesión de Estrategia Técnica gratuita y descubre cómo pasar de Junior a Senior en menos tiempo del que imaginas.

  • Diagnóstico personalizado de tu nivel técnico
  • Plan de acción para conseguir tu próximo ascenso
  • Mentoría 1-a-1 enfocada en resultados

Plazas disponibles limitadas

Compartir artículo