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.
Cada pregunta viene con una respuesta modelo con la que puedes comparar la tuya.
1
¿Cómo funciona HashMap internamente, y en qué se diferencia ConcurrentHashMap?
Respuesta
HashMap guarda entradas en un array de buckets indexado por el hash de la clave; las colisiones forman una lista enlazada que se convierte en árbol rojo-negro pasando ocho nodos. No es thread-safe — un resize concurrente puede corromper la estructura. ConcurrentHashMap logra thread safety bloqueando solo el bucket que se está escribiendo (CAS más synchronized sobre el nodo) en lugar de todo el mapa, así que las lecturas son lock-free y las escrituras compiten solo en el mismo bucket.
2
Explica el contrato de equals() y hashCode(). ¿Qué se rompe si lo violas?
Respuesta
Si dos objetos son iguales por equals(), deben devolver el mismo hashCode; objetos desiguales pueden compartir un hash pero idealmente no lo hacen. Violarlo rompe toda colección basada en hash: un objeto guardado en un HashMap cae en un bucket, y una búsqueda posterior calcula un hash distinto, busca en otro bucket, y reporta la entrada como ausente. El bug clásico es mutar un campo usado en hashCode después de insertar el objeto en un set.
3
¿Qué es el garbage collection y qué colectores ofrece la JVM?
Respuesta
GC recupera memoria del heap para objetos ya no alcanzables desde las GC roots. El heap es generacional — la mayoría de los objetos mueren jóvenes, así que las colecciones menores en la generación joven son baratas. Los colectores incluyen Serial y Parallel para throughput, G1 como default moderno que balancea tiempo de pausa y throughput, y ZGC o Shenandoah para heaps muy grandes con pausas de submilisegundos.
4
¿Cuál es la diferencia entre excepciones checked y unchecked?
Respuesta
Las excepciones checked extienden Exception y deben declararse o manejarse — representan condiciones recuperables que el caller debería prever, como IOException. Las unchecked extienden RuntimeException y señalan errores de programación como NullPointerException o IllegalArgumentException. La guía práctica es usar unchecked para bugs y checked con moderación, porque forzar a cada caller a manejar una excepción a menudo produce bloques catch vacíos.
5
¿Qué es Dependency Injection en Spring y qué tipos de inyección existen?
Respuesta
DI significa que un objeto recibe sus dependencias del contenedor en lugar de construirlas, lo que desacopla clases y las hace testeables. Spring soporta inyección por constructor, por setter, y por campo. La inyección por constructor es la preferida: las dependencias se vuelven final, el objeto nunca está a medio construir, y la clase no puede esconder una lista de dependencias que crece sin control como sí puede la inyección por campo.
6
¿Cuál es la diferencia entre @Component, @Service, @Repository y @Controller?
Respuesta
Las cuatro registran la clase como un bean de Spring y son técnicamente intercambiables para el escaneo. Difieren en intención y en los extras que Spring agrega: @Repository traduce excepciones de persistencia a la jerarquía DataAccessException de Spring, @Controller y @RestController participan en el mapeo de requests, y @Service es un marcador semántico para lógica de negocio. Usar la anotación específica documenta la capa y habilita el comportamiento correcto.
7
¿Qué es una transacción? Explica los niveles de aislamiento.
Respuesta
Una transacción es una unidad de trabajo que satisface ACID: o hace commit completo o hace rollback. Los niveles de aislamiento intercambian consistencia por concurrencia — Read Uncommitted permite lecturas sucias, Read Committed las previene, Repeatable Read también previene lecturas no repetibles, y Serializable previene phantom reads a costa del throughput. La mayoría de los sistemas corren en Read Committed y manejan las anomalías restantes con locking optimista.
8
¿Cuál es la diferencia entre ArrayList y LinkedList? ¿Cuándo usas cada uno?
Respuesta
ArrayList está respaldado por un array redimensionable: el acceso indexado es O(1), pero insertar o eliminar en el medio desplaza elementos. LinkedList es una lista doblemente enlazada: insertar y eliminar en un nodo conocido es O(1), pero el acceso indexado es O(n) y cada elemento carga overhead de nodo. En la práctica ArrayList gana casi siempre porque la localidad de caché de la CPU vence a la complejidad teórica; LinkedList es principalmente útil como Deque.
9
¿Qué son volatile y synchronized? ¿Cuándo usas cada uno?
Respuesta
volatile garantiza visibilidad — una escritura es inmediatamente visible para otros hilos y las lecturas nunca se cachean — pero no provee atomicidad, así que los contadores volatile igual pierden incrementos. synchronized provee tanto visibilidad como exclusión mutua, así que solo un hilo ejecuta el bloque a la vez. Usa volatile para una bandera simple leída por muchos hilos, synchronized o una clase Atomic cuando la operación es read-modify-write.
10
Explica el problema N+1 en Hibernate y cómo evitarlo.
Respuesta
N+1 ocurre cuando cargas N entidades padre con una consulta, y luego accedes lazy a una colección en cada una, produciendo N consultas adicionales. Suele aparecer como una página rápida en tests con tres filas e inutilizable con tres mil. Las soluciones son un JOIN FETCH en JPQL, un entity graph, batch fetching vía @BatchSize, o una proyección que seleccione exactamente los campos que la vista necesita.
11
¿Cuál es la diferencia entre String, StringBuilder y StringBuffer?
Respuesta
String es inmutable, así que cada concatenación asigna un objeto nuevo — construir un string en un loop con + es cuadrático. StringBuilder es un buffer mutable y es la herramienta correcta para ese loop. StringBuffer es lo mismo que StringBuilder pero con métodos synchronized; es esencialmente legacy, ya que compartir un buffer mutable entre hilos es raro y usualmente necesita locking más grueso de todos modos.
12
¿Qué es la Stream API de Java y cuándo no deberías usarla?
Respuesta
Los streams expresan operaciones sobre colecciones de forma declarativa como un pipeline de operaciones intermedias lazy y una operación terminal que dispara la ejecución. Brillan para filtrado, mapeo y agregación porque la intención se mantiene legible. Evítalos en loops muy calientes donde la abstracción cuesta tiempo medible, para lógica con efectos secundarios, y para cualquier caso donde un for plano se leería más claro.