Las entrevistas de Java backend cubren la semántica central del lenguaje, colecciones, concurrencia, Spring, y cómo razonas sobre la JVM. Aquí están las preguntas más frecuentes, cada una con una respuesta modelo. Middle: comprensión más profunda, optimización y situaciones reales de trabajo.
1
¿Cómo funciona HashMap internamente, y qué cambió en Java 8?
Respuesta
Las entradas viven en un array de buckets indexado por una función del hash de la clave; las colisiones forman una lista enlazada en el mismo bucket. Java 8 agregó la treeificación: una vez que un bucket tiene más de ocho entradas se convierte en un árbol rojo-negro, así que la búsqueda en el peor caso pasó de O(n) a O(log n). También distribuye el hash con h ^ (h >>> 16) para que los bits altos influyan en el índice de bits bajos.
2
¿Qué garantiza volatile, y qué no?
Respuesta
Garantiza visibilidad y orden: una escritura se publica a otros hilos inmediatamente y las lecturas no se cachean en un registro. No garantiza atomicidad, así que un contador volatile igual pierde incrementos, porque ++ es read-modify-write y dos hilos pueden leer el mismo valor. Usa volatile para una bandera que muchos hilos leen, y AtomicInteger o synchronized cuando la operación combina lectura y escritura.
3
Explica la propagación de @Transactional. ¿Cuándo importa REQUIRES_NEW?
Respuesta
La propagación decide qué pasa cuando un método transaccional se llama desde dentro de otra transacción. REQUIRED, el default, se une a la existente — así que un rollback en cualquier parte hace rollback de todo. REQUIRES_NEW suspende la transacción externa y empieza la suya, que es lo que quieres para un log de auditoría o una notificación que debe sobrevivir a que el caller falle. El bug clásico es esperar que REQUIRED aísle un fallo cuando no puede.
4
Ves el problema N+1 en un endpoint respaldado por Hibernate. ¿Cómo lo encuentras y arreglas?
Respuesta
Lo encuentras activando el logging de SQL y contando consultas para un request — la firma es un select seguido de cientos casi idénticos. Pasa cuando cargas padres y luego tocas una colección lazy en cada uno. Las soluciones son JOIN FETCH en JPQL, un entity graph, @BatchSize para traer en grupos, o una proyección que seleccione solo los campos que la respuesta necesita. Cambiar a EAGER no es una solución; mueve el problema.
5
¿Cuál es la diferencia entre Stream y un for loop, y cuándo es mejor un loop?
Respuesta
Un stream describe un pipeline de operaciones lazy ejecutado por una llamada terminal, lo que mantiene el filtrado y mapeo legibles. Un loop es mejor en un hot path donde la abstracción cuesta tiempo medible, cuando la lógica tiene efectos secundarios, y cuando un break en el medio lo expresaría más claro que un takeWhile. Los streams paralelos son la verdadera trampa — solo ayudan para trabajo grande limitado por CPU y usualmente perjudican en otros casos.
6
Un servicio es lento y el CPU está bajo. ¿Dónde buscas?
Respuesta
CPU bajo con mal throughput apunta a espera en vez de cómputo: IO bloqueante, un pool de conexiones agotado, contención de locks, o una llamada externa sin timeout. Toma un thread dump y ve en qué están realmente estacionados los hilos. La inanición del pool de conexiones es el culpable más común, y usualmente se rastrea hasta una transacción mantenida abierta a través de una llamada de red.
7
¿Cuál es la diferencia entre la auto-configuración de Spring Boot y una clase @Configuration simple?
Respuesta
La auto-configuración es configuración condicional entregada por un starter: clases anotadas con @ConditionalOnClass o @ConditionalOnMissingBean que aplican solo cuando una dependencia está en el classpath y tú no definiste el bean tú mismo. Esa última parte es lo que la hace sentir mágica y predecible a la vez — tu propio bean siempre gana, que es cómo se sobreescribe cualquier default.
8
¿Cuál es la diferencia entre `wait()`, `sleep()` y `join()`?
Respuesta
sleep pausa el hilo actual y mantiene cualquier lock que tenga — que es cómo produces un deadlock pareciendo inocente. wait libera el monitor y estaciona el hilo hasta notify, y por eso solo se puede llamar dentro de un bloque synchronized. join espera a que otro hilo termine. wait y notify pertenecen a Object en vez de a Thread porque el lock pertenece al objeto.
9
¿Qué hace `transient`, y dónde muerde la serialización?
Respuesta
transient excluye un campo de la serialización, así que en la deserialización vuelve con el default del tipo. Se usa para cualquier cosa derivada, cualquier cosa enorme, y cualquier cosa secreta — una contraseña o una conexión viva no tiene por qué estar en un stream de bytes. La trampa más amplia es que la serialización congela la forma de tu clase en un formato: cambia los campos y los datos viejos fallan al leerse a menos que serialVersionUID y la compatibilidad se hayan planeado, por eso la mayoría de los equipos usan JSON o protobuf en su lugar.