El problema que resuelve Optional
Un método que quizá no tenga una respuesta siempre ha tenido una opción incómoda en Java: devolver null y confiar en que quien lo llama recuerde comprobarlo. Normalmente no lo hace, y el resultado es un NullPointerException que aparece muy lejos del método que devolvió el null.
Optional<T> es una pequeña caja que o bien contiene un valor o bien está explícitamente vacía. Al devolver Optional<User> en lugar de User, un método les dice a quienes lo llaman "esto podría no encontrar nada" en su propia firma, y el compilador los guía hacia manejar ese caso.
Crear un Optional
Hay tres métodos de fábrica, y elegir el correcto importa:
Optional.of(value): el valor debe ser no nulo, o lanzaNullPointerExceptionen el acto.Optional.ofNullable(value): vacío si el valor es null, presente en caso contrario. Úsalo para envolver algo que podría ser null.Optional.empty(): un optional vacío.
Un error común es recurrir a Optional.of(x) sobre un valor que podría ser null: eso anula su propósito y lanza justo la excepción que intentabas evitar. Ante la duda, usa ofNullable.
Leer el valor de forma segura
Una vez que tienes un optional, el objetivo es sacar el valor sin dar por hecho que está ahí. La herramienta brusca es get(), y casi nunca deberías usarla:
Optional<String> name = Optional.empty();
String value = name.get(); // lanza NoSuchElementException - un NPE disfrazado
En su lugar, proporciona un valor de respaldo o reacciona a la presencia. orElse devuelve un valor por defecto cuando está vacío; ifPresent ejecuta código solo cuando existe un valor:
Usa orElseGet(supplier) en lugar de orElse cuando el valor por defecto sea costoso de construir: el supplier solo se ejecuta si el optional está realmente vacío. El argumento de orElse siempre se evalúa, incluso cuando no hace falta.
orElseThrow para "esto debería existir"
A veces el vacío sí es un error: un valor de configuración obligatorio, un usuario que debería estar en la base de datos. orElseThrow convierte un optional vacío en una excepción clara que tú eliges:
Esto se lee mucho mejor que un bloque if (opt.isPresent()) y hace explícito el fallo justo en el punto donde ocurre.
Transformar con map y filter
La verdadera recompensa es el encadenamiento. map aplica una función al valor solo si está presente y deja vacío un optional vacío, así que transformas sin tocar nunca un null. filter descarta el valor si no supera una prueba.
Si tu función de mapeo devuelve a su vez un Optional, usa flatMap en lugar de map para evitar un incómodo Optional<Optional<T>>. Esto refleja la distinción map/flatMap que viste con los streams: un Optional se comporta de forma muy parecida a un stream de cero o un elemento.
Dónde encaja Optional (y dónde no)
Optional está diseñado como tipo de retorno para métodos que legítimamente pueden no producir un resultado: los ejemplos canónicos son Stream.findFirst(), las búsquedas estilo Map y el parseo. Úsalo ahí y tu API documenta sus propios huecos.
No está pensado para:
- Campos. No es serializable y añade un objeto por campo. Usa referencias normales y valida en el constructor.
- Parámetros de método. Quien llama tiene entonces que envolver los argumentos en
Optional.of(...), lo cual es más ruidoso que una sobrecarga o un parámetro que admita null. - Colecciones. Devuelve una
Listvacía, no unOptional<List>. Una colección vacía ya significa "nada".
Tratado como tipo de retorno, Optional convierte los errores silenciosos de null en avisos en tiempo de compilación para manejar el caso ausente.
Siguiente: Excepciones
Optional maneja con limpieza el caso cotidiano de "puede que no haya valor", pero algunos fallos son genuinamente excepcionales: un archivo que no se abre, una red que se cae, una entrada que no se puede parsear. Java modela esos casos con excepciones, y esa es la siguiente página.
Preguntas frecuentes
¿Qué es Optional en Java y por qué usarlo?
Optional<T> es un contenedor que o bien guarda un valor o bien está vacío. Existe para hacer explícito en el tipo de retorno de un método que "puede que no haya valor", de modo que se anima a quien lo llama a manejar el caso vacío en vez de olvidar una comprobación de null y toparse con un NullPointerException en tiempo de ejecución. Úsalo sobre todo como tipo de retorno de métodos que quizá no produzcan un resultado, como una búsqueda que no encuentra nada.
¿Cuál es la diferencia entre Optional.of y Optional.ofNullable?
Optional.of(x) lanza NullPointerException de inmediato si x es null: úsalo cuando sabes que el valor no es null. Optional.ofNullable(x) devuelve un Optional vacío cuando x es null y uno presente en caso contrario: úsalo cuando el valor podría ser null. Optional.empty() te da directamente un optional vacío.
¿Debería llamar a Optional.get() para leer el valor?
Evita el get() directo: lanza NoSuchElementException si el optional está vacío, que no es más que un NullPointerException con otro nombre. Prefiere orElse, orElseGet, orElseThrow, ifPresent o map para que el caso vacío siempre se maneje. Si tienes que comprobarlo primero, protégete con isPresent(), pero los métodos funcionales son más limpios.