Java NullPointerException – Detectar, Corrigir e Melhores Práticas

java.lang.NullPointerException é uma das exceções mais populares em java programação. Qualquer pessoa trabalhando em java deve ter visto isso surgir do nada no programa standalone java, bem como na aplicação web java.

java.lang.NullPointerException

NullPointerException é uma exceção de tempo de execução, então não precisamos capturá-la no programa. NullPointerException é levantada em uma aplicação quando estamos tentando fazer alguma operação em null onde um objeto é requerido. Algumas das razões comuns para NullPointerException em programas java são:

  1. Invocar um método em uma instância de objeto mas em tempo de execução o objeto é nulo.
  2. Acessar variáveis de uma instância de objeto que é nula em tempo de execução.
  3. Lançar nulo no programa
  4. Acessar índice ou modificar valor de um índice de um array que é nulo
  5. Verificar o comprimento de um array que é nulo em tempo de execução.

Exemplos de NullPointerException em Java

Vamos dar uma olhada em alguns exemplos de NullPointerException em programas Java.

1. NullPointerException ao chamar um método de instância

public class Temp {

	public static void main(String[] args) {

		Temp t = initT();
		
		t.foo("Hi");
		
	}

	private static Temp initT() {
		return null;
	}

	public void foo(String s) {
		System.out.println(s.toLowerCase());
	}
}

Ao executarmos o programa acima, ele lança a seguinte mensagem de erro NullPointerException.

Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:7)

Estamos recebendo NullPointerException na instrução t.foo("Oi"); porque “t” está nulo aqui.

2. Java NullPointerException ao acessar/modificar um campo de um objeto nulo

public class Temp {

	public int x = 10;
	
	public static void main(String[] args) {

		Temp t = initT();
		
		int i = t.x;
		
	}

	private static Temp initT() {
		return null;
	}

}

O programa acima lança o seguinte rastreamento de pilha de NullPointerException.

Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:9)

NullPointerException está sendo lançada na instrução int i = t.x; porque “t” está nulo aqui.

3. Java NullPointerException quando null é passado como argumento de método

public class Temp {

	public static void main(String[] args) {

		foo(null);

	}

	public static void foo(String s) {
		System.out.println(s.toLowerCase());
	}
}

Esta é uma das ocorrências mais comuns de `java.lang.NullPointerException` porque é o chamador quem está passando o argumento nulo.

A imagem abaixo mostra a exceção de ponteiro nulo quando o programa acima é executado no Eclipse IDE.

4. java.lang.NullPointerException quando nulo é lançado

public class Temp {

	public static void main(String[] args) {

		throw null;
	}

}

Abaixo está a pilha de exceção do programa acima, mostrando NullPointerException por causa da instrução `throw null;`.

Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:5)

5. NullPointerException ao obter o comprimento de um array nulo

public class Temp {

	public static void main(String[] args) {

		int[] data = null;
		
		int len = data.length;
	}

}
Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:7)

6. NullPointerException ao acessar o valor do índice de um array nulo

public class Temp {

	public static void main(String[] args) {

		int[] data = null;
		
		int len = data[2];
	}

}
Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:7)

7. NullPointerException ao sincronizar em um objeto nulo

public class Temp {

	public static String mutex = null;
	
	public static void main(String[] args) {

		synchronized(mutex) {
			System.out.println("synchronized block");
		}		
	}
}

O synchronized(mutex) irá lançar NullPointerException porque o objeto “mutex” é nulo.

8. Status HTTP 500 java.lang.NullPointerException

Às vezes recebemos uma página de erro de resposta de uma aplicação web Java com uma mensagem de erro como “Status HTTP 500 – Erro Interno do Servidor” e a causa raiz como java.lang.NullPointerException.

Para isso, editei o projeto de exemplo do Spring MVC e alterei o método HomeController conforme abaixo.

@RequestMapping(value = "/user", method = RequestMethod.POST)
	public String user(@Validated User user, Model model) {
		System.out.println("User Page Requested");
		System.out.println("User Name: "+user.getUserName().toLowerCase());
		System.out.println("User ID: "+user.getUserId().toLowerCase());
		model.addAttribute("userName", user.getUserName());
		return "user";
	}

A imagem abaixo mostra a mensagem de erro gerada pela resposta da aplicação web.

Aqui está o rastreamento de pilha da exceção:

HTTP Status 500Internal Server Error

Type Exception Report

Message Request processing failed; nested exception is java.lang.NullPointerException

Description The server encountered an unexpected condition that prevented it from fulfilling the request.

Exception

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
	org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
	org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
	org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
Root Cause

java.lang.NullPointerException
	com.journaldev.spring.controller.HomeController.user(HomeController.java:38)
	sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	java.lang.reflect.Method.invoke(Method.java:498)

A causa raiz é NullPointerException na instrução user.getUserId().toLowerCase() porque user.getUserId() está retornando null.

Como detectar java.lang.NullPointerException

Detectar o NullPointerException é muito fácil, basta olhar para o rastreamento de exceções e ele mostrará o nome da classe e o número da linha da exceção. Em seguida, examine o código e veja o que poderia ser nulo, causando o NullPointerException. Apenas olhe todos os exemplos acima, é muito claro no rastreamento de pilha o que está causando a exceção de ponteiro nulo.

Como corrigir o NullPointerException

java.lang.NullPointerException é uma exceção não verificada, então não precisamos capturá-la. As exceções de ponteiro nulo podem ser evitadas usando verificações nulas e técnicas de codificação preventiva. Veja abaixo exemplos de código mostrando como evitar java.lang.NullPointerException.

if(mutex ==null) mutex =""; //codificação preventiva
		
synchronized(mutex) {
	System.out.println("synchronized block");
}
//usando verificações nulas
if(user!=null && user.getUserName() !=null) {
System.out.println("User Name: "+user.getUserName().toLowerCase());
}
if(user!=null && user.getUserName() !=null) {
	System.out.println("User ID: "+user.getUserId().toLowerCase());
}

Práticas recomendadas de codificação para evitar NullPointerException

1. Vamos considerar a função abaixo e procurar por cenários que causem exceção de ponteiro nulo.

public void foo(String s) {
    if(s.equals("Test")) {
	System.out.println("test");
    }
}

A exceção de NullPointerException pode ocorrer se o argumento estiver sendo passado como nulo. O mesmo método pode ser escrito como abaixo para evitar NullPointerException.

public void foo(String s) {
	if ("Test".equals(s)) {
		System.out.println("test");
	}
}

2. Também podemos adicionar uma verificação de nulo para o argumento e lançar IllegalArgumentException se necessário.

public int getArrayLength(Object[] array) {
	
	if(array == null) throw new IllegalArgumentException("array is null");
	
	return array.length;
}

3. Podemos usar o operador ternário como mostrado no código de exemplo abaixo.

String msg = (str == null) ? "" : str.substring(0, str.length()-1);

4. Use String.valueOf() em vez do método toString(). Por exemplo, verifique que o código do método println() de PrintStream está definido como abaixo.

public void println(Object x) {
        String s = String.valueOf(x);
        synchronized (this) {
            print(s);
            newLine();
        }
    }

O trecho de código abaixo mostra o exemplo em que o método valueOf() é usado em vez de toString().

Object mutex = null;

//imprime null
System.out.println(String.valueOf(mutex));

//irá lançar java.lang.NullPointerException
System.out.println(mutex.toString());

5. Escreva métodos que retornem objetos vazios em vez de nulo sempre que possível, por exemplo, lista vazia, string vazia, etc.

6. Existem alguns métodos definidos em classes de coleção para evitar NullPointerException, você deve usá-los. Por exemplo, contains(), containsKey() e containsValue().

Referência: Documento da API

Source:
https://www.digitalocean.com/community/tutorials/java-lang-nullpointerexception