Monte um pequeno test runner só com o JDK, a partir de uma anotação e de reflexão, teste código package-private com –patch-module e distribua um serviço Java como JAR modular, imagem de runtime do jlink e instalador do jpackage.
O JDK do Java chama e serve HTTP sem bibliotecas. Aprenda o HttpClient, síncrono e assíncrono, e depois construa um pequeno serviço de tarefas em JSON sobre o HttpServer embutido, com virtual threads, status codes certos e um encerramento limpo.
O Java consegue rodar cem mil tarefas bloqueantes em poucas threads do sistema operacional. Veja como virtual threads montam e desmontam, o que ainda as prende, por que ScopedValue substitui ThreadLocal e como StructuredTaskScope cancela o trabalho que falha.
Os executors do Java rodam suas tarefas num pool fixo de threads e devolvem um Future. Aprenda submit, invokeAll, cancelamento, cadeias de CompletableFuture, timeouts, semáforos e blocking queues, com saída que nunca varia.
Duas threads Java que atualizam a mesma variável podem perder escritas em silêncio. Veja por que count++ é uma condição de corrida, como synchronized, volatile, atomics e locks resolvem isso, e como forçar e detectar um deadlock.
Onde o Java guarda suas variáveis e objetos, como o garbage collector decide o que liberar, por que um programa com garbage collection ainda pode vazar ou ficar sem memória, e como o JIT deixa o código mais rápido quanto mais ele roda.
Monte um projeto Java de dois módulos só com javac, jar e java. Veja como pacotes, imports, o classpath, arquivos JAR e o module-info.java se encaixam, e o que o Maven e o Gradle acrescentam por cima.
O Java deixa qualquer referência ser null, então um valor ausente pode derrubar o código longe de onde sumiu. Objects e Optional deixam a ausência visível, e java.time faz você dizer de qual momento, em qual cidade, está falando.
Lambdas em Java transformam um pequeno comportamento num valor, e streams encadeiam esses valores em pipelines preguiçosos. Veja como um pipeline roda de verdade, quais collectors usar e quando um laço simples fica mais claro.
Generics deixam o compilador verificar o que entra numa lista, numa caixa ou num método, e depois apagam essa informação antes de o programa rodar. Aprenda limites, wildcards, PECS e como ler uma assinatura do JDK rodando programas pequenos.