Entrevistas de Java backend cobrem a semântica central da linguagem, collections, concorrência, Spring e como você raciocina sobre a JVM. Abaixo estão as perguntas mais frequentes, cada uma com uma resposta modelo. Middle: compreensão mais profunda, otimização e situações reais de trabalho.
1
Como o HashMap funciona internamente, e o que mudou no Java 8?
Resposta
As entradas ficam em um array de buckets indexado por uma função do hash da chave; colisões formam uma lista encadeada dentro do mesmo bucket. O Java 8 trouxe a treeification: assim que um bucket tem mais de oito entradas ele vira uma árvore rubro-negra, então o pior caso de busca foi de O(n) para O(log n). Além disso o hash é espalhado com h ^ (h >>> 16) para que os bits mais altos influenciem o índice de bits mais baixos.
2
O que volatile garante, e o que não garante?
Resposta
Garante visibilidade e ordenação: uma escrita é publicada para outras threads imediatamente e as leituras não são cacheadas em um registrador. Não garante atomicidade, então um contador volatile ainda perde incrementos, porque ++ é read-modify-write e duas threads podem ler o mesmo valor. Use volatile para uma flag lida por muitas threads, e AtomicInteger ou synchronized quando a operação combina leitura e escrita.
3
Explique a propagação do @Transactional. Quando o REQUIRES_NEW importa?
Resposta
A propagação decide o que acontece quando um método transacional é chamado de dentro de outra transação. REQUIRED, o padrão, se junta à existente — então um rollback em qualquer lugar reverte tudo. REQUIRES_NEW suspende a transação externa e inicia a sua própria, o que é o que você quer para um log de auditoria ou uma notificação que precisa sobreviver à falha de quem chamou. O bug clássico é esperar que REQUIRED isole uma falha quando ele não consegue.
4
Você vê o problema N+1 num endpoint apoiado em Hibernate. Como encontrar e corrigir?
Resposta
Você encontra ativando o log de SQL e contando as queries de uma requisição — a assinatura é um select seguido de centenas quase idênticas. Acontece quando você carrega os pais e depois toca uma coleção lazy em cada um. As correções são JOIN FETCH em JPQL, um entity graph, @BatchSize para buscar em grupos, ou uma projeção que selecione só os campos que a resposta precisa. Trocar para EAGER não é uma correção; só move o problema.
5
Qual é a diferença entre Stream e um loop for, e quando um loop é melhor?
Resposta
Um stream descreve um pipeline de operações lazy executado por uma chamada terminal, o que mantém filtragem e mapeamento legíveis. Um loop é melhor num hot path onde a abstração custa tempo mensurável, quando a lógica tem efeitos colaterais, e quando um break no meio expressaria melhor do que um takeWhile. Streams paralelos são a verdadeira armadilha — eles só ajudam em trabalho grande e limitado por CPU e geralmente atrapalham nos demais casos.
6
Um serviço está lento e a CPU está baixa. Onde você olha?
Resposta
CPU baixa com throughput ruim aponta para espera em vez de processamento: IO bloqueante, um pool de conexões esgotado, contenção de locks, ou uma chamada externa sem timeout. Tire um thread dump e veja em que as threads estão realmente paradas. A inanição do pool de conexões é o culpado mais comum, e geralmente remonta a uma transação mantida aberta durante uma chamada de rede.
7
Qual é a diferença entre a auto-configuração do Spring Boot e uma classe @Configuration simples?
Resposta
Auto-configuração é configuração condicional trazida por um starter: classes anotadas com @ConditionalOnClass ou @ConditionalOnMissingBean que se aplicam só quando uma dependência está no classpath e você mesmo não definiu o bean. Essa última parte é o que a faz parecer mágica e previsível ao mesmo tempo — o seu próprio bean sempre vence, e é assim que você sobrescreve qualquer padrão.
8
Qual é a diferença entre `wait()`, `sleep()` e `join()`?
Resposta
sleep pausa a thread atual e mantém qualquer lock que ela segure — o que é como você consegue um deadlock parecendo inocente. wait libera o monitor e estaciona a thread até notify, e é exatamente por isso que só pode ser chamado dentro de um bloco synchronized. join espera outra thread terminar. wait e notify pertencem a Object, não a Thread, porque o lock pertence ao objeto.
9
O que o `transient` faz, e onde a serialização morde?
Resposta
transient exclui um campo da serialização, então na desserialização ele volta com o valor padrão do tipo. Usa-se para tudo que é derivado, tudo que é enorme e tudo que é secreto — uma senha ou uma conexão viva não têm nada que fazer num stream de bytes. A armadilha maior é que a serialização congela o formato da sua classe: mude os campos e os dados antigos falham ao ler, a menos que serialVersionUID e compatibilidade tenham sido planejados, por isso a maioria das equipes usa JSON ou protobuf em vez disso.