· Vladyslav Baidak · Backend Development · 5 min de leitura
Java Backend: Tratamento de Exceções em JAX-RS & Java Servlets

Introdução
Ao desenvolver aplicações RESTful utilizando Java Backend, mais cedo ou mais tarde irá enfrentar a necessidade de tratar os erros HTTP de forma personalizada. Por exemplo, o frontend pode necessitar de uma espécie de representação genérica de um erro HTTP (normalmente em formato JSON), para que possa facilmente aplicar uma única lógica ao mesmo.
No mundo do Java Backend, a tecnologia mais popular para tratar pedidos HTTP é Java Servlets. Esta é perfeitamente adequada para tratar pedidos HTTP gerais, bem como recursos estáticos. A API RESTful, por outro lado, é normalmente tratada por outra tecnologia, chamada JAX-RS.
Neste artigo, vamos implementar o mecanismo personalizado de tratamento de erros HTTP para ambas as tecnologias, Java Servlets e JAX-RS.
Requisitos
Em geral, queremos ter a seguinte resposta para qualquer erro HTTP:
{
"code": 404,
"reasonPhrase": "Not Found",
"message": "An HTTP error occurred"
}
onde:
codeé um código de estado HTTPreasonPhraseé uma descrição de um erro HTTPmessage– qualquer mensagem que queira apresentar (opcional)
Primeiros Passos
O primeiro passo será criar a entidade Java ErrorResponse:
import java.io.Serializable;
public class ErrorResponse implements Serializable {
private static final long serialVersionUID = 1L;
private final int code;
private final String reasonPhrase;
private String message;
public ErrorResponse(int code, String reasonPhrase) {
this.code = code;
this.reasonPhrase = reasonPhrase;
}
public ErrorResponse(int code, String reasonPhrase, String message) {
this.code = code;
this.reasonPhrase = reasonPhrase;
this.message = message;
}
public int getCode() {
return code;
}
public String getReasonPhrase() {
return reasonPhrase;
}
public String getMessage() {
return message;
}
}
A entidade ErrorResponse será serializada num objeto JSON apropriado por alguma biblioteca Java JSON (por exemplo, Jackson)
Dependências
Basicamente, precisaríamos das seguintes dependências:
dependencies {
providedCompile 'javax.ws.rs:javax.ws.rs-api:2.0.1'
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
compile 'com.fasterxml.jackson.core:jackson-core:2.5.4'
}
A nossa aplicação Java Backend está a correr dentro de um contentor JBOSS, por isso estamos a adicionar apenas dependências javax.* provided. A implementação real será fornecida pelo contentor.
Parte JAX-RS
É muito comum adicionar um prefixo /api para todos os endpoints RESTful, a fim de os distinguir de outros recursos web. Isto pode ser facilmente conseguido adicionando a seguinte configuração ao ficheiro WEB-INF/web.xml:
<servlet>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
</servlet>
<servlet-mapping>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/api</param-value>
</context-param>
Uma vez que o JBOSS utiliza o RESTEasy como implementação JAX-RS, este requer que seja definido um parâmetro de contexto adicional, resteasy.servlet.mapping.prefix (apontando para um prefixo)
Para tratar exceções de qualquer tipo, o JAX-RS fornece a interface javax.ws.rs.ext.ExceptionMapper:
package javax.ws.rs.ext;
import javax.ws.rs.core.Response;
public interface ExceptionMapper<E extends Throwable> {
Response toResponse(E exception);
}
Para tratar qualquer exceção, quem a implementa deve fazer o seguinte:
Implementar a interface
javax.ws.rs.ext.ExceptionMapperMarcar a classe de implementação com a anotação
@Provider(dojavax.ws.rs.ext package), o que a tornará detetável durante a fase de análise de providers do JAX-RS
Ter um exception mapper para o tipo de exceção específico também será aplicado a qualquer um dos seus tipos derivados.
Digamos que quer tratar todos os tipos de erros HTTP (por exemplo, 404, 500) da mesma forma, mas de forma personalizada. O JAX-RS tem exceções separadas para cada um dos códigos de estado de erro HTTP (por exemplo, javax.ws.rs.NotFoundException para 404), cada uma das quais, por sua vez, estende a exceção pai javax.ws.rs.ClientErrorException. Assim, para as tratar a todas, simplesmente implementa um exception mapper para esta exceção pai:
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class ClientErrorExceptionMapper implements ExceptionMapper<ClientErrorException> {
@Override
public Response toResponse(ClientErrorException exception) {
Response response = exception.getResponse();
Response.StatusType statusType = response.getStatusInfo();
int statusCode = statusType.getStatusCode();
String reasonPhrase = statusType.getReasonPhrase();
String message = "An HTTP error occurred";
ErrorResponse entity = new ErrorResponse(statusCode, reasonPhrase, message);
return Response.ok()
.entity(entity)
.build();
}
}
Agora, sempre que ocorrer um erro HTTP no endpoint /api, este será tratado pelo JAX-RS.
Parte Java Servlet
Qualquer recurso que não esteja localizado sob /api é tratado pelos Java Servlets e, por isso, requer que seja implementado o mesmo mecanismo de tratamento de erros.
Para tratar erros HTTP, precisamos de estender um HTTP Servlet e substituir o seu método service(HttpServletRequest request, HttpServletResponse response). Estamos interessados apenas em tratar códigos de estado HTTP sem sucesso, por isso mantemos a lógica existente para os códigos de estado HTTP de sucesso (intervalo entre 200 e 299) e aplicamos a lógica de tratamento de erros HTTP aos restantes:
package com.swupp.pro.apsbau.commons.servlet;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.swupp.pro.apsbau.commons.rest.ErrorResponse;
import com.swupp.pro.apsbau.commons.rest.ExtendedMediaType;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/error-handler-servlet")
public class ErrorHandlerServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
int statusCode = response.getStatus();
if (statusCode >= 200 && statusCode < 299) {
super.service(request, response);
} else {
response.setContentType(ExtendedMediaType.APPLICATION_JSON_UTF_8);
Response.Status status = Response.Status.fromStatusCode(statusCode);
ErrorResponse errorResponse =
new ErrorResponse(statusCode, status.getReasonPhrase());
ObjectMapper objectMapper = new ObjectMapper();
String entity = objectMapper.writeValueAsString(errorResponse);
try (PrintWriter writer = response.getWriter()) {
writer.write(entity);
writer.flush();
}
}
}
}
Aqui, a anotação @WebServlet é utilizada para tornar o ErrorHandlerServlet disponível sob o URI específico, que é /error-handler-servlet. Agora, para ser utilizado no tratamento de qualquer erro HTTP, um endpoint ErrorHandlerServlet deve ser especificado no ficheiro WEB-INF/web.xml:
<error-page>
<location>/error-handler-servlet</location>
</error-page>
É isso, e agora o nosso JSON bonito será devolvido para todos os erros, o que é exatamente o que queríamos.
Se achou este artigo útil, não hesite em clicar nos botões de partilha abaixo 😉 Cuide-se e leia mais artigos sobre Desenvolvimento Java no nosso blog.



