Java Depurar
O programa compila, roda, não dá erro nenhum… e mostra o resultado errado. Esse é o tipo de defeito mais difícil, porque o Java não aponta a linha. Depurar é o trabalho de detetive de descobrir onde o programa se desviou do que você queria. Nesta aula você aprende as ferramentas, da mais simples (println) à mais poderosa (o depurador).
- O método de depuração: reproduzir, formular uma hipótese, observar, corrigir.
- Usar
printlnde forma estratégica (e tirar depois). - Ler um stack trace para achar a linha de uma exceção.
- Usar o depurador de linha de comando
jdb:stop at,run,print,locals,next,cont. - O que o depurador da IDE oferece: breakpoints, step over/into, watches.
- Verificar suposições com
asserte a opção-ea. - Registrar o que acontece com logs (
java.util.loggingeSystem.Logger).
A ideia: o detetive não chuta
A palavra inglesa para defeito é bug ("inseto"); depurar é debug, "tirar os insetos". Quem começa costuma depurar "no chute": muda uma linha, roda, muda outra, roda… Funciona às vezes, mas costuma criar defeitos novos. O detetive faz diferente:
- Reproduzir: achar uma entrada que sempre mostra o erro.
- Saber o que devia acontecer: calcule à mão o resultado certo.
- Observar: olhar os valores das variáveis no meio do caminho, até achar o primeiro ponto em que eles divergem do esperado.
- Corrigir a causa (e não o sintoma), e rodar de novo. Se houver testes, eles confirmam que nada mais quebrou.
O passo 3 é o que as ferramentas desta aula fazem: deixar você ver dentro do programa enquanto ele roda.
O caso: a média errada
A Ana escreveu um programa para calcular a média das notas de um aluno: 8, 7, 10 e 6. À mão: (8 + 7 + 10 + 6) / 4 = 31 / 4 = 7.75.
public class Media {
public static void main(String[] args) {
int[] notas = {8, 7, 10, 6};
int soma = 0;
for (int i = 1; i < notas.length; i++) {
soma += notas[i];
}
double media = soma / notas.length;
System.out.println("Média: " + media);
}
}
$ javac -g Media.java $ java Media Média: 5.0
5.0, e não 7.75. Nenhuma mensagem de erro. Hora de investigar. (O -g no javac guarda informações extras de depuração no .class, como os nomes das variáveis locais; você vai precisar dele no jdb.)
1. O println estratégico
A ferramenta mais simples: pôr System.out.println nos pontos onde você quer ver os valores. "Estratégico" quer dizer: não espalhe prints em todo lugar, ponha onde as suas suposições podem estar erradas, e mostre nome e valor de cada variável:
public class Media {
public static void main(String[] args) {
int[] notas = {8, 7, 10, 6};
int soma = 0;
for (int i = 1; i < notas.length; i++) {
System.out.println("DEBUG i=" + i + " nota=" + notas[i] + " soma=" + soma);
soma += notas[i];
}
System.out.println("DEBUG soma final=" + soma + " quantidade=" + notas.length);
double media = soma / notas.length;
System.out.println("Média: " + media);
}
}
$ java Media.java DEBUG i=1 nota=7 soma=0 DEBUG i=2 nota=10 soma=7 DEBUG i=3 nota=6 soma=17 DEBUG soma final=23 quantidade=4 Média: 5.0
Leia com olho de detetive: a soma deveria ser 31, mas é 23. Faltam 8, justamente a primeira nota. E a primeira linha mostra por quê: o laço começa em i=1, e os arrays começam no índice 0 (veja Arrays). Primeiro defeito achado: int i = 1 deveria ser int i = 0.
Corrigido o laço, a Ana roda de novo, agora com prints da soma e da média:
$ java Media.java DEBUG soma=31 quantidade=4 DEBUG media=7.0 Média: 7.0
A soma agora está certa (31), mas a média deu 7.0. O problema está entre os dois prints, na linha double media = soma / notas.length;. soma e notas.length são int, então a divisão é inteira: 31 / 4 = 7, e só depois o 7 vira 7.0 ao ser guardado no double (veja Operadores Aritméticos). O conserto é converter antes de dividir: (double) soma / notas.length.
$ java Media.java Média: 7.75
- Comece com uma marca fácil de achar (
DEBUG) e apague todos depois; prints esquecidos poluem a saída do programa. - Mostre o nome junto do valor:
"soma=" + soma, nunca sósoma. - Para não misturar com a saída normal, use
System.err.println: ela vai para a "saída de erros", que dá para separar no terminal (java Media 2>/dev/nullesconde os prints de depuração). - Se você se pega pondo os mesmos prints de novo e de novo, é hora de um depurador ou de logs.
Missão: este é o programa original, com os dois defeitos. Ponha prints de depuração, ache-os e corrija até a saída ser Média: 7.75. Depois, apague os prints.
public class Main {
public static void main(String[] args) {
int[] notas = {8, 7, 10, 6};
int soma = 0;
for (int i = 1; i < notas.length; i++) {
soma += notas[i];
}
double media = soma / notas.length;
System.out.println("Média: " + media);
}
}
public class Main {
public static void main(String[] args) {
int[] notas = {8, 7, 10, 6};
int soma = 0;
for (int i = 0; i < notas.length; i++) {
soma += notas[i];
}
double media = (double) soma / notas.length;
System.out.println("Média: " + media);
}
}
2. Ler o stack trace
Quando o defeito causa uma exceção, o Java já faz metade do trabalho de detetive: ele imprime o stack trace ("rastro da pilha"), a lista de métodos que estavam rodando no momento do erro. Aqui, a Ana separou o cálculo num método e errou a condição do laço (<= em vez de <):
public class Boletim {
static double media(int[] notas) {
int soma = 0;
for (int i = 0; i <= notas.length; i++) {
soma += notas[i];
}
return (double) soma / notas.length;
}
public static void main(String[] args) {
int[] notas = {8, 7, 10, 6};
System.out.println("Média: " + media(notas));
}
}
$ java Boletim.java Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4 at Boletim.media(Boletim.java:5) at Boletim.main(Boletim.java:12)
- Exception in thread "main"
- Uma exceção não tratada parou o programa (no thread principal).
- java.lang.ArrayIndexOutOfBoundsException
- O quê: índice fora dos limites do array.
- Index 4 out of bounds for length 4
- Detalhes: tentou usar o índice 4 num array de tamanho 4 (os índices válidos vão de 0 a 3).
- at Boletim.media(Boletim.java:5)
- Onde: a primeira linha
até o lugar exato do erro, o métodomedia, linha 5 (soma += notas[i];). - at Boletim.main(Boletim.java:12)
- Quem chamou: o
mediafoi chamado pelomain, na linha 12. Lendo de cima para baixo, você vai "voltando" pelo caminho do programa.
Com o stack trace, a pergunta deixa de ser "onde está o erro?" e vira "por que i chegou a 4 na linha 5?". Resposta: o <= deixa o laço rodar com i == notas.length. Em programas grandes, o stack trace tem dezenas de linhas, muitas de bibliotecas (at java.base/…, at org.junit…); procure a primeira linha que é do seu código. A aula O que são Exceções explica as partes com mais calma, e a Exceções Comuns lista as mais frequentes.
Missão: rode o Boletim, leia o stack trace no editor, vá até a linha apontada e corrija o laço para a saída ser Média: 7.75.
public class Main {
static double media(int[] notas) {
int soma = 0;
for (int i = 0; i <= notas.length; i++) {
soma += notas[i];
}
return (double) soma / notas.length;
}
public static void main(String[] args) {
int[] notas = {8, 7, 10, 6};
System.out.println("Média: " + media(notas));
}
}
public class Main {
static double media(int[] notas) {
int soma = 0;
for (int i = 0; i < notas.length; i++) {
soma += notas[i];
}
return (double) soma / notas.length;
}
public static void main(String[] args) {
int[] notas = {8, 7, 10, 6};
System.out.println("Média: " + media(notas));
}
}
3. O depurador: jdb
Um depurador (debugger) roda o seu programa em câmera lenta. Você marca uma linha com um ponto de parada (breakpoint); quando o programa chega lá, ele congela, e você pode olhar todas as variáveis, avançar uma linha por vez, e continuar. Nada de editar o código para pôr prints.
O JDK traz um depurador de linha de comando, o jdb (Java Debugger). Ele é simples e pouco usado no dia a dia (as IDEs têm depuradores visuais, na próxima seção), mas mostra muito bem como todo depurador funciona. Uma sessão real com o Media.java original (o do laço começando em 1), compilado com javac -g:
$ jdb Media
Initializing jdb ...
> stop at Media:6
Deferring breakpoint Media:6.
It will be set after the class is loaded.
> run
run Media
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: Set deferred breakpoint Media:6
Breakpoint hit: "thread=main", Media.main(), line=6 bci=34
6 soma += notas[i];
main[1] locals
Method arguments:
args = instance of java.lang.String[0] (id=431)
Local variables:
notas = instance of int[4] (id=432)
soma = 0
i = 1
main[1] print soma
soma = 0
main[1] print notas[i]
notas[i] = 7
main[1] next
>
Step completed: "thread=main", Media.main(), line=5 bci=40
5 for (int i = 1; i < notas.length; i++) {
main[1] next
>
Step completed:
Breakpoint hit: "thread=main", Media.main(), line=6 bci=34
6 soma += notas[i];
main[1] print soma
soma = 7
main[1] cont
>
Breakpoint hit: "thread=main", Media.main(), line=6 bci=34
6 soma += notas[i];
main[1] print i
i = 3
main[1] print soma
soma = 17
main[1] clear Media:6
Removed: breakpoint Media:6
main[1] cont
> Média: 5.0
The application exited
Passo a passo do que a Ana fez:
| Ela digitou | O que aconteceu |
|---|---|
stop at Media:6 | Marcou um ponto de parada na linha 6 da classe Media (a linha que soma). Como o programa ainda não começou, o jdb "adia" (deferring) a marca até a classe ser carregada. |
run | Começou o programa. Ele rodou até a linha 6 e congelou: Breakpoint hit … line=6. O jdb mostra a linha que vai rodar (ainda não rodou). O prompt muda para main[1]: o thread main está parado. |
locals | Mostrou todas as variáveis locais. A pista: na primeira passada pelo laço, i = 1. Deveria ser 0! |
print soma, print notas[i] | Mostrou uma variável ou expressão: a soma ainda é 0, e a nota da vez é 7 (a segunda nota; a primeira, 8, foi pulada). |
next | Executou a linha atual e parou na próxima (step over). Foi para a linha 5, o i++ e o teste do for. Outro next voltou à linha 6, onde está o breakpoint. |
cont | Continuou rodando até o próximo breakpoint (a próxima volta do laço). |
clear Media:6 | Tirou o breakpoint. |
cont | O programa rodou até o fim: Média: 5.0 e The application exited. |
Repare: a Ana descobriu o mesmo defeito do println, mas sem mudar uma linha do código. Outros comandos úteis do jdb:
| Comando | O que faz |
|---|---|
stop in Classe.metodo | Para na entrada de um método |
step | Executa a linha e, se ela chamar um método, entra nele (step into) |
step up | Roda até o fim do método atual e volta para quem chamou (step out) |
where | Mostra a pilha de chamadas, como um stack trace |
dump objeto | Mostra todos os atributos de um objeto |
clear | Lista os breakpoints |
help | Lista todos os comandos |
exit | Sai do jdb (e encerra o programa) |
4. O depurador da IDE
No dia a dia, quase ninguém usa o jdb: as IDEs (IntelliJ IDEA, VS Code, Eclipse, NetBeans; veja Editores e IDEs) têm um depurador visual que faz o mesmo com cliques. Por baixo, elas usam o mesmo mecanismo do jdb. O que você vai encontrar:
- Breakpoint
- Clique na margem esquerda, ao lado do número da linha: aparece uma bolinha vermelha. É o
stop at. - Botão Debug (o "inseto")
- Roda o programa no modo de depuração, em vez do botão Run. É o
rundojdb. O programa para na bolinha, e a linha fica destacada. - Painel de variáveis
- Mostra sozinho todas as variáveis locais e os atributos dos objetos, que você pode abrir como pastas. É o
localse odump, sempre atualizado. - Step Over
- Executa a linha e para na próxima (
next). IntelliJ: F8; VS Code: F10. - Step Into
- Entra no método chamado na linha (
step). IntelliJ: F7; VS Code: F11. - Step Out
- Termina o método atual e volta para quem chamou (
step up). IntelliJ: Shift+F8; VS Code: Shift+F11. - Resume / Continue
- Continua até o próximo breakpoint (
cont). IntelliJ: F9; VS Code: F5. - Watches (expressões vigiadas)
- Uma lista de expressões que você escolhe, como
soma / notas.length, recalculadas a cada passo. É umprintautomático. - Breakpoint condicional
- Clique com o botão direito na bolinha e escreva uma condição, como
i == 3: o programa só para quando ela for verdadeira. Ótimo para laços com milhares de voltas. - Call stack (pilha de chamadas)
- A lista de métodos em execução, como o stack trace; clicar numa linha mostra as variáveis daquele método. É o
where.
Os nomes e atalhos mudam um pouco de uma IDE para outra, mas as ideias são exatamente estas. Se você entendeu a sessão do jdb, sabe usar qualquer depurador.
5. assert e a opção -ea
Um assert ("afirmar") é uma suposição escrita no código: "neste ponto, isto tem de ser verdade". Se não for, o programa para com um AssertionError, bem perto da origem do problema, em vez de seguir com um valor maluco.
public class Turma {
static double media(int[] notas) {
assert notas.length > 0 : "lista de notas vazia";
int soma = 0;
for (int nota : notas) {
soma += nota;
}
return (double) soma / notas.length;
}
public static void main(String[] args) {
int[] semNotas = {};
System.out.println("Média: " + media(semNotas));
}
}
- assert
- Palavra-chave do Java.
- notas.length > 0
- A condição que você supõe verdadeira.
- : "lista de notas vazia"
- Opcional: a mensagem do erro, se a condição for falsa.
Agora o detalhe importante: os assert vêm desligados. Para ligar, use a opção -ea (enable assertions, "ligar as afirmações") no java:
$ java Turma.java
Média: NaN « assert desligado: 0.0 / 0 dá NaN ("não é um número")
$ java -ea Turma.java
Exception in thread "main" java.lang.AssertionError: lista de notas vazia
at Turma.media(Turma.java:3)
at Turma.main(Turma.java:13)
Sem o -ea, o programa segue e mostra NaN, um valor sem sentido que pode ir parar num relatório. Com o -ea, ele para na linha 3 e diz por quê. Funciona igual com classes compiladas: java -ea Turma.
assert para validar dados do usuário
Como os assert ficam desligados quando o programa roda normalmente, eles servem para pegar erros de programação enquanto você desenvolve e testa, não para conferir o que vem de fora. Para recusar um valor inválido (um depósito negativo, um campo vazio), use if e throw new IllegalArgumentException(…) (veja throw e throws), que funcionam sempre. E nunca ponha no assert algo que precisa acontecer, como assert lista.remove(x): com o assert desligado, a remoção também some.
6. Logs: um diário do programa
Os prints de depuração somem quando você os apaga. Mas, num programa que roda todo dia (um sistema de loja, um servidor), você quer um diário permanente: o que aconteceu, quando, e com que gravidade. Isso é um log ("diário de bordo"). A diferença para o println: cada mensagem tem um nível de importância, data e hora, e você liga ou desliga níveis sem mexer no código.
java.util.logging
O Java traz um sistema de logs pronto, no pacote java.util.logging:
import java.util.logging.Level;
import java.util.logging.Logger;
public class Loja {
private static final Logger LOG = Logger.getLogger(Loja.class.getName());
public static void main(String[] args) {
LOG.info("Caixa aberto");
LOG.warning("Estoque baixo: café");
LOG.fine("Detalhe que não aparece");
LOG.log(Level.SEVERE, "Falha ao gravar a venda", new IllegalStateException("disco cheio"));
System.out.println("Fim do expediente");
}
}
$ javac Loja.java $ java Loja out. 16, 2026 6:50:21 PM Loja main INFORMAÇÕES: Caixa aberto out. 16, 2026 6:50:21 PM Loja main ADVERTÊNCIA: Estoque baixo: café out. 16, 2026 6:50:21 PM Loja main GRAVE: Falha ao gravar a venda java.lang.IllegalStateException: disco cheio at Loja.main(Loja.java:11) Fim do expediente
- Logger.getLogger(Loja.class.getName())
- Pega um logger ("registrador") com o nome da classe. É costume ter um por classe, guardado num atributo
private static final. - LOG.info / LOG.warning
- Registram uma mensagem com o nível
INFO(informação) ouWARNING(aviso). O idioma do computador traduz o nível:INFORMAÇÕES,ADVERTÊNCIA. - LOG.fine
- Nível
FINE(detalhe). Não apareceu: por padrão, sóINFOpara cima é mostrado. Os detalhes ficam no código, prontos para ligar quando precisar investigar, sem poluir a saída no dia a dia. - LOG.log(Level.SEVERE, "…", exceção)
- Nível
SEVERE(GRAVE), com a exceção e o stack trace junto. - out. 16, 2026 6:50:21 PM Loja main
- Cada mensagem ganha data, hora, classe e método de onde veio.
Os logs vão para a saída de erros, não para a saída normal. Escondendo a saída de erros, só sobra o println:
$ java Loja.java 2>/dev/null Fim do expediente
Os níveis, do mais grave ao mais detalhado: SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST.
System.Logger Java 9+
O System.Logger é uma "fachada" mais simples, que já vem no System sem import. Por padrão, ele usa o java.util.logging por baixo (por isso a saída tem o mesmo formato), mas, num projeto grande, pode ser ligado a outra biblioteca de logs sem mudar o seu código. E aceita "buracos" {0}, {1} para os valores:
public class Loja2 {
private static final System.Logger LOG = System.getLogger("loja");
public static void main(String[] args) {
LOG.log(System.Logger.Level.INFO, "Caixa aberto");
LOG.log(System.Logger.Level.WARNING, "Estoque baixo: {0} ({1} unidades)", "café", 2);
LOG.log(System.Logger.Level.DEBUG, "Detalhe que não aparece");
}
}
$ java Loja2.java out. 16, 2026 6:50:14 PM Loja2 main INFORMAÇÕES: Caixa aberto out. 16, 2026 6:50:14 PM Loja2 main ADVERTÊNCIA: Estoque baixo: café (2 unidades)
Em projetos profissionais, é comum usar bibliotecas de log de fora do JDK (SLF4J com Logback, Log4j 2), adicionadas pelo Maven; a ideia de níveis é a mesma.
Qual ferramenta usar?
| Situação | Ferramenta |
|---|---|
| O programa parou com uma exceção | Leia o stack trace: a primeira linha at do seu código |
| Resultado errado num programa pequeno | println estratégico, ou o depurador |
| Resultado errado num programa grande, ou num laço longo | Depurador com breakpoint (condicional) |
| "Isto nunca deveria acontecer" | assert, rodando com -ea durante o desenvolvimento |
| Saber o que aconteceu ontem, num programa que roda sempre | Logs |
| Garantir que o defeito não volta | Um teste que reproduz o defeito |
Quando dá errado
assert "não funciona"
O que aconteceu: a condição é falsa, mas o programa não para. Os assert estão desligados. Como resolver: rode com java -ea …. Nas IDEs, a opção fica nas configurações de execução (VM options: -ea).
jdb não mostra as variáveis locais
O que aconteceu: a classe foi compilada sem informação de depuração, e os nomes das variáveis locais não estão no .class. O mesmo efeito aparece até nas mensagens de exceção. Sem -g:
$ javac Pedido.java && java Pedido Validando pedido... Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "<local1>" is null at Pedido.validar(Pedido.java:18) at Pedido.main(Pedido.java:7) $ javac -g Pedido.java && java Pedido Validando pedido... Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "nome" is null at Pedido.validar(Pedido.java:18) at Pedido.main(Pedido.java:7)
<local1> quer dizer "a variável local número 1", sem nome. Como resolver: compile com javac -g. (O Maven e o Gradle já compilam com -g, como mostra o [debug release 25] na saída do Maven.)
O que aconteceu: a linha marcada não é executada (está num if que não entrou, num método que ninguém chama) ou é uma linha sem código (em branco, comentário, só uma }). Como resolver: ponha o breakpoint numa linha com instrução, mais cedo no caminho, e avance com next/step.
O que aconteceu: você mudou o .java e rodou o .class antigo (esqueceu o javac), ou corrigiu o sintoma num lugar e a causa está em outro. Como resolver: recompile (ou use java Arquivo.java, que compila na hora). Volte ao método: ache o primeiro ponto em que um valor diverge do esperado.
Referência
| Item | O que faz |
|---|---|
System.err.println("DEBUG x=" + x) | Print de depuração na saída de erros |
javac -g Arquivo.java | Compila com informação de depuração |
jdb Classe | Abre o depurador para a classe |
stop at Classe:linha / stop in Classe.metodo | Breakpoint numa linha / num método |
run / cont | Começa / continua até o próximo breakpoint |
next / step / step up | Step over / step into / step out |
print expr / locals / where | Mostra uma expressão / as variáveis locais / a pilha |
assert condição : "mensagem"; | Suposição verificada só com -ea |
java -ea Classe | Roda com os assert ligados |
Logger.getLogger(nome) | Logger do java.util.logging |
System.getLogger(nome) | Logger da fachada System.Logger |
Exercícios
1. Ligar os asserts
Complete o comando para rodar a classe Turma com os assert ligados:
2. Breakpoint no jdb
Complete para parar na linha 12 da classe Boletim:
3. Onde está o erro?
Dado o stack trace abaixo, em que linha de Pedido.java a exceção aconteceu?
$ java Pedido Validando pedido... Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "nome" is null at Pedido.validar(Pedido.java:18) at Pedido.main(Pedido.java:7)
4. No depurador, qual comando executa a linha atual e, se ela chamar um método, entra nele?
5. Por que o LOG.fine("…") não apareceu na saída?
6. Para recusar um depósito negativo digitado pelo usuário, o certo é:
Missão final: a Ana anotou as temperaturas de uma semana de inverno na serra, todas abaixo de zero. O programa diz que a maior foi 0, uma temperatura que nem aparece na lista! Investigue (com prints, se quiser) e corrija para mostrar Maior: -2. Dica: qual é o valor de maior antes da primeira comparação?
public class Main {
public static void main(String[] args) {
int[] temperaturas = {-5, -2, -8, -3, -6};
int maior = 0;
for (int t : temperaturas) {
if (t > maior) {
maior = t;
}
}
System.out.println("Maior: " + maior);
}
}
public class Main {
public static void main(String[] args) {
int[] temperaturas = {-5, -2, -8, -3, -6};
int maior = temperaturas[0];
for (int t : temperaturas) {
if (t > maior) {
maior = t;
}
}
System.out.println("Maior: " + maior);
}
}
Resumo
- Depurar é método: reproduzir, saber o resultado certo, observar até achar o primeiro valor que diverge, corrigir a causa.
printlnestratégico: nome e valor, marcaDEBUG, apagar depois (ouSystem.err).- Stack trace: tipo e mensagem da exceção; a primeira linha
atdo seu código é onde aconteceu; as de baixo, quem chamou. - Depurador: breakpoint, executar até ele, olhar variáveis, avançar (
next= step over,step= step into), continuar. Ojdbfaz isso no terminal; as IDEs, com cliques e watches. assert condição : "msg"só vale comjava -ea; serve para erros de programação, não para validar dados.- Logs (
java.util.logging,System.Logger) registram o que acontece com nível, data e hora;FINE/DEBUGficam escondidos por padrão.