Is it possible to include a message in a BadRequestException so when the user sees a response code a 400, he/she can also figure out why?
The scenario would be something like this, simplified:
public Entity getEntityWithOptions(#PathParam("id") String id, #QueryParam("option") String optValue) {
if (optValue != null) {
// Option is an enum
try {
Option option = Option.valueOf(optValue);
} catch (IllegalArgumentException e) {
throw new BadRequestException(e.getMessage());
}
return new Entity(option);
}
return new Entity();
}
I know this can be done returning a Response object instead, but I wouldn't want that.
Is this possible? Maybe with an ExceptionMapper<BadRequestException>? Or this cannot be done since BadRequestException is already a Jersey-specific exception?
There is a really simple approach to this as shown below.
Response.ResponseBuilder resp_builder=Response.status(Response.Status.BAD_REQUEST);
resp_builder.entity(e.getMessage());//message you need as the body
throw new WebApplicationException(resp_builder.build());
if you need to add headers, response media type or some other functionality, ResponseBuilder provides them all.
You can throw a CustomException and map it to a CustomExceptionMapper to provide customized response.
public class CustomException extends RuntimeException {
public CustomException(Throwable throwable) {
super(throwable);
}
public CustomException(String string, Throwable throwable) {
super(string, throwable);
}
public CustomException(String string) {
super(string);
}
public CustomException() {
super();
}
}
#Provider
public class CustomExceptionMapper implements ExceptionMapper<CustomException> {
private static Logger logger = Logger.getLogger(CustomExceptionMapper.class.getName());
/**
* This constructor is invoked when exception is thrown, after
* resource-method has been invoked. Using #provider.
*/
public CustomExceptionMapper() {
super();
}
/**
* When exception is thrown by the jersey container.This method is invoked
*/
public Response toResponse(CustomException ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex);
Response.ResponseBuilder resp = Response.status(Response.Status.BAD_REQUEST)
.entity(ex.getMessage());
return resp.build();
}
}
Use the CustomException in your code like this.
public Entity getEntityWithOptions(#PathParam("id") String id,
#QueryParam("option") String optValue)
throws CustomException {
if (optValue != null) {
// Option is an enum
try {
Option option = Option.valueOf(optValue);
} catch (IllegalArgumentException e) {
throw new CustomException(e.getMessage(),e);
}
return new Entity(option);
}
return new Entity();
}
Instead of message, you can also construct an object and pass it to mapper through CustomException.
You should create a custom exception like this
public class CustomBadReq extends WebApplicationException {
public CustomBadReq(String message) {
super(Response.status(Response.Status.BAD_REQUEST)
.entity(message).type(MediaType.TEXT_PLAIN).build());
}
}
See also this
You can do it using the BadRequestException(Response response)constructor.
For example:
String msg = e.getMessage();
throw new BadRequestException(Response.status(BAD_REQUEST)
.entity(msg).build());
Related
When the user creates a record with a name that's already exists in the DB , I'm returning a specific Exception.
#PostMapping("/campaigns")
public ResponseEntity<CampaignDTO> saveCampaign(#RequestBody CampaignDTO campaignDTO) throws ApiErrorResponse, Exception {
if (this.campaignService.getCampaignByName(campaignDTO.getName()) != null) {
throw new IllegalArgumentException("The value already exists!");
}
if (campaignDTO.getProducts() == null) {
ApiErrorResponse errorReponseDto = new ApiErrorResponse("No Products attached");
throw errorReponseDto;
}
campaignDTO = campaignService.saveCampaign(campaignDTO);
ResponseEntity<CampaignDTO> responseEntity = new ResponseEntity<>(campaignDTO , HttpStatus.CREATED);
return responseEntity; // return 201
}
And the Exception that I want to return to the Client is:
public class ApiErrorResponse extends Throwable {
private final String error;
//Any addtional info you might later want to add to it
public ApiErrorResponse(String error){
this.error = error;
}
public String getError(){
return this.error;
}
}
However , when I throw
IllegalArgumentException("The value already exists!")
It is caught by
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
}
}
How can we prevent this , and return "ApiErrorResponse" when the user inserts the same name ?
I want to return my Exception , not anything else.
Here you have few decision. I will mark 2 of them.
1st is to return directly BadRequest for example with specific DTO - It is not best example with the Throwable, but you can create new ErrorResponseDTO:
#PostMapping("/campaigns")
public ResponseEntity<?> saveCampaign(#RequestBody CampaignDTO campaignDTO) throws ApiErrorResponse, Exception {
if (this.campaignService.getCampaignByName(campaignDTO.getName()) != null) {
ApiErrorResponse errorReponseDto = new ApiErrorResponse("The value already exists!");
return ResponseEntity.badRequest().body(errorReponseDto) // return 400
}
if (campaignDTO.getProducts() == null) {
ApiErrorResponse errorReponseDto = new ApiErrorResponse("No Products attached");
return new ResponseEntity<>(errorReponseDto , HttpStatus.BAD_REQUEST); // return 400
}
campaignDTO = campaignService.saveCampaign(campaignDTO);
ResponseEntity<CampaignDTO> responseEntity = new ResponseEntity<>(campaignDTO , HttpStatus.CREATED);
return responseEntity; // return 201
}
Other way is to use #ControllerAdvice which will handle exception and will return what you want. This advice will be triggered after you throw the exception:
#ControllerAdvice
public class MyAdvice {
#ExceptionHandler(value = ApiErrorResponse.class)
public ResponseEntity<MyErrorResponse> handleException(ApiErrorResponse exception) {
return return ResponseEntity.badRequest().body(MyErrorResponse)
}
}
a better way to define a global exception class and a global exception handler.
the global exception class:
#EqualsAndHashCode(callSuper = true)
#Data
public class GlobalErrorInfoException extends RuntimeException {
private String message;
private HttpStatus status;
private Long timestamp;
public GlobalErrorInfoException(HttpStatus status, String message) {
super(message);
this.status = status;
this.message = message;
this.timestamp = System.currentTimeMillis();
}
}
the global exception handler
#RestControllerAdvice
#Slf4j
public class GlobalErrorInfoHandler {
// other ExceptionHandler
// GlobalErrorInfoException handler
#ExceptionHandler(value = GlobalErrorInfoException.class)
public ResponseEntity<?> errorHandlerOverJson(GlobalErrorInfoException e) {
log.error("Global Exception ", e);
return new ResponseEntity<>(e.getMessage(), e.getStatus());
}
#ExceptionHandler(Exception.class)
public ResponseEntity<?> handleRuntimeException(Exception e) {
log.error("error ", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("internal server error");
}
}
use
// your throw
throw new GlobalErrorInfoException(HttpStatus.BAD_REQUEST, "The value already exists!");
you can also handler IllegalArgumentException in the global exception handler.
I have a java SDK,which use OkHttp client(4.0.0) to get token from IAM server and return token to application.The relation may like this:Applicaiton Sync call SDK,SDK Async call IAM.Refer to this answerJava - Retrieving Result from OkHttp Asynchronous GET,the code like:
The Async Class:
class BaseAsyncResult<T> {
private final CompletableFuture<T> future = new CompletableFuture<>();
T getResult() {
try {
return future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return null;
}
void onFailure(IOException e) {
future.completeExceptionally(e);
}
void onResponse(Response response) throws IOException {
String bodyString = Objects.requireNonNull(response.body()).string();
future.complete(IasClientJsonUtil.json2Pojo(bodyString, new TypeReference<T>() {}));
}
}
Okhttp call like this:
public void invoke(Request request, BaseAsyncResult result) {
okHttpClient.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(#NotNull Call call, #NotNull IOException e) {
result.onFailure(e);
}
#Override
public void onResponse(#NotNull Call call, #NotNull Response response) throws IOException {
result.onResponse(response);
}
});
}
The application use sdk code like,iasClient is a wrapper of okhttp client :
BaseAsyncResult<AuthenticationResponse> iasAsyncResult = new BaseAsyncResult();
iasClient.invoke(request, iasAsyncResult);
AuthenticationResponse result = iasAsyncResult.getResult();
The erroe message:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to x.x.x.AuthenticationResponse
What have I missed?
You need to make sure jackson knows which class to deserialize the value to . In this case, you are asking Jackson to deserialize the response to a TypeReference , which will resolve to a Map by default unless you specify the class (in this case, AuthenticationResponse ) . The Future resolves to a linkedHashMap due to this and causes the class cast.
try replacing the below line .
future.complete(IasClientJsonUtil.json2Pojo(bodyString, new TypeReference<T>() {}));
with
future.complete(IasClientJsonUtil.json2Pojo(bodyString, new TypeReference<AuthenticationResponse>() {}));
One method from #Arpan Kanthal is add a private Class type variable to BaseAsyncResult and then use that class in your json2Pojo function,then the BaseAsyncResult may like this:
public class BaseAsyncResult<T> {
private final CompletableFuture<T> future = new CompletableFuture<>();
private Class<T> classType;
public BaseAsyncResult(Class<T> classType) {
this.classType = classType;
}
public T getResult() {
try {
return future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return null;
}
void onFailure(IOException e) {
future.completeExceptionally(e);
}
void onResponse(Response response) throws IOException {
future.complete(JacksonUtil.json2Pojo(response.body().string(), classType));
}
}
I have an already implemented controller method which returns a value or throws an exception.
#Override
#PostMapping(Endpoint.SUB_RESOURCE)
#ResponseStatus(HttpStatus.CREATED)
public EmployeeBankAccountOutputDto createBankAccount(#ApiParam("Account data") #RequestBody final EmployeeBankAccountInputDto accountCreationDto) {
try {
return service.createBankAccount(accountCreationDto);
} catch (InvalidAccountDataException ex) {
throw new InvalidAccountDataResponseException(ex, ex.getErrors());}
} catch (CountryNotFoundException ex) {
throw new CountryNotFoundResponseException(ex.getCountryCode(), ex);
}
}
In this code I want to just log exception message in warning level and not throwing exception because I don't want to see something like :
14:37:02,610 ERROR [] c.m.w.ApiExceptionHandler:135 - Account API error occurred: 400
So if I just put
log.warn(ex.getMessage()); to the first catch statement, it is giving
missing return statement
error for that catch block as normal. So How can handle not throwing exception to calling api and just send the exception message properly, because return type of the method is a dto?
If the account could not be created you don't want to return nothing.
At least you want to return a 400 error response to signal to the client the issue.
You could use ResponseEntity as return type of your method to signal the response code. It also means that you will have to change the way of return the object in the successful case too.
You could change the method in this way if you don't want to transmit the specific error message but only the error code :
public ResponseEntity<EmployeeBankAccountOutputDto> createBankAccount(#ApiParam("Account data") #RequestBody final EmployeeBankAccountInputDto accountCreationDto) {
try {
return ResponseEntity.ok(service.createBankAccount(accountCreationDto));
} catch (InvalidAccountDataException ex) {
log.warn(ex.getMessage());
return ResponseEntity.badRequest();
}
}
If you also want to transmit the specific error message you could use the body of the response :
public ResponseEntity<EmployeeBankAccountOutputDto> createBankAccount(#ApiParam("Account data") #RequestBody final EmployeeBankAccountInputDto accountCreationDto) {
try {
return ResponseEntity.ok(service.createBankAccount(accountCreationDto));
} catch (InvalidAccountDataException ex) {
log.warn(ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
}
}
Note that if you need to perform this processings in multiple controllers it probably means that an exception handler would be a neater approach.
For example to handle InvalidAccountDataException in an uniform way :
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
Logger LOGGER = LoggerFactory.getLogger(MyExceptionHandler.class);
#ExceptionHandler(value = { InvalidAccountDataException.class })
protected ResponseEntity<Object> handleGenericExceptions(InvalidAccountDataException ex, WebRequest request) {
log.warn(ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
}
}
You can add more methods to handle more types of exception if required of course.
If you don't throw the exception from the 1st catch block and an exception is thrown and caught in that case what should the method return?
That is why you are getting a compiler error as there will be no return value as either a return statement is missing or an exception is not thrown which would've caused the execution flow to stop abruptly, so instead you can do the following:
public EmployeeBankAccountOutputDto createBankAccount(#ApiParam("Account data") #RequestBody final EmployeeBankAccountInputDto accountCreationDto) {
EmployeeBankAccountInputDto employeeBankAccountInputDto = null;
try {
employeeBankAccountInputDto = service.createBankAccount(accountCreationDto);
} catch (InvalidAccountDataException ex) {
log.warn(ex.getMessage());
} catch (CountryNotFoundException ex) {
throw new CountryNotFoundResponseException(ex.getCountryCode(), ex);
}
return employeeBankAccountInputDto;
}
You should ideally wrap the returned object in a ResponseEntity and add a message & response code (as davidxxx explains in his comment).
public ResponseEntity<EmployeeBankAccountOutputDto> createBankAccount(#ApiParam("Account data") #RequestBody final EmployeeBankAccountInputDto accountCreationDto) {
EmployeeBankAccountInputDto employeeBankAccountInputDto = null;
try {
employeeBankAccountInputDto = service.createBankAccount(accountCreationDto);
} catch (InvalidAccountDataException ex) {
log.warn(ex.getMessage());
return new ResponseEntity(employeeBankAccountInputDto, HttpStatus.BAD_REQUEST);
} catch (CountryNotFoundException ex) {
throw new CountryNotFoundResponseException(ex.getCountryCode(), ex);
}
return new ResponseEntity(employeeBankAccountInputDto, HttpStatus.OK);
}
I have the following insert/update methods in my service:
#Override
public void insertEntity(Entity entity) {
try {
entityDao.insert(entityMapper.entityToEntityDO(entity));
} catch (DataIntegrityViolationException ex){
if(ex.getCause() instanceof SQLIntegrityConstraintViolationException) {
SQLIntegrityConstraintViolationException violationEx = (SQLIntegrityConstraintViolationException) ex.getCause();
if(violationEx.getErrorCode() == 1048 && "23000".equals(violationEx.getSQLState())) {
throw new FieldCannotBeNullException(violationEx.getMessage());
}
}
throw ex;
}
}
#Override
public void updateEntity(Entity entity) {
try {
entityDao.update(entityMapper.entityToEntityDO(entity));
} catch (DataIntegrityViolationException ex){
if(ex.getCause() instanceof SQLIntegrityConstraintViolationException) {
SQLIntegrityConstraintViolationException violationEx = (SQLIntegrityConstraintViolationException) ex.getCause();
if(violationEx.getErrorCode() == 1048 && "23000".equals(violationEx.getSQLState())) {
throw new FieldCannotBeNullException(violationEx.getMessage());
}
}
throw ex;
}
}
As you can see, the actual logic of insertEntity and updateEntity is very simple. In order to throw a custom Exception, I did some database error code check. Since the two methods all need this kind of checking, the code duplicated in both methods, which is obviously a code smell.
How can I eliminate this kind of code duplication?
Extract the common catch-block to a method which throws DataIntegrityViolationException.
You can create Interface like this:
public interface ConsumerWithException<T, V extends Exception> {
/**
* Performs this operation on the given argument.
*
* #param t the input argument
*/
void accept(T t) throws V;
}
Use it a private method like:
private void action(ConsumerWithException<Entity, DataIntegrityViolationException> doAction, Entity entity){
try {
doAction.accept(entity);
} catch (DataIntegrityViolationException ex){
if(ex.getCause() instanceof SQLIntegrityConstraintViolationException) {
SQLIntegrityConstraintViolationException violationEx = (SQLIntegrityConstraintViolationException) ex.getCause();
if(violationEx.getErrorCode() == 1048 && "23000".equals(violationEx.getSQLState())) {
throw new FieldCannotBeNullException(violationEx.getMessage());
}
}
throw ex;
}
}
You can put the code inside the catch block into a separate method.
Alternatively, You can catch Exception and write a handler method to handle the exceptions if in future you expect to handle multiple exceptions there.
You can declare your methods to throw the exception, then try/catch in one place where your methods are called. For example:
public void insertEntity(Entity entity) throws DataIntegrityViolationException {}
public void updateEntity(Entity entity) throws DataIntegrityViolationException {}
try {
insertEntity(entity);
updateEntity(entity);
catch (DataIntegrityViolationException e) {
// handle exception
}
My Java application requires a retry logic on remote calls failures.
These remote calls are:
scattered all over the application
pertain to different Remote Service classes.
Also, the retry logic may have varying retry interval and varying retry attempts.
I need a generic retry() implementation which can make appropriate method calls depending on from where it is called. Below is a simple code illustration of I am looking for. I know we can attempt to do this using java reflection, but, is there a framework or an open source available somewhere which is read-to-use?
try {
ClassA objA = remoteServiceA.call(paramA1, paramA2, ...);
} catch (Exception e){
ClassA objA = (ClassA)retry(remoteService, listOfParams, ..); // generic method call
}
..
try {
ClassB objB = remoteServiceB.call(paramB1, paramB2, ...);
} catch (Exception e){
ClassA objB = (ClassB)retry(remoteService, listOfParams, ..); // generic method call
}
As already suggested, you should use AOP and Java annotations. I would recommend a read-made mechanism from jcabi-aspects (I'm a developer):
#RetryOnFailure(attempts = 3, delay = 5)
public String load(URL url) {
return url.openConnection().getContent();
}
Read also this blog post: http://www.yegor256.com/2014/08/15/retry-java-method-on-exception.html
Update: Check RetryFunc from Cactoos.
This is a book example of where aspectj (or aop in general) can be used, see 8.2.7 Example in Spring documentation and 5 Reasons Java Developers Should Learn and Use AspectJ.
Basically an aspect intercepts all calls to given methods (specified using annotation, naming convention, whatever) and retries.
Assume you have a method, that need to retied at every 500ms and upto 5 times.
Current class:
public class RemoteCaller{
Service serviceCaller;
public void remoteCall(String message) {
serviceCaller.updateDetails( this.message);
return null;
}
}
Modified approach:
public class RetriableHelper<T> implements Callable<T> {
private Callable<T> task;
private int numberOfRetries;
private int numberOfTriesLeft;
private long timeToWait;
public RetriableHelper(int numberOfRetries, long timeToWait, Callable<T> task) {
this.numberOfRetries = numberOfRetries;
numberOfTriesLeft = numberOfRetries;
this.timeToWait = timeToWait;
this.task = task;
}
public T call() throws Exception {
while (true) {
try {
return task.call();
} catch (InterruptedException e) {
throw e;
} catch (CancellationException e) {
throw e;
} catch (Exception e) {
numberOfTriesLeft--;
if (numberOfTriesLeft == 0) {
throw e;
}
Thread.sleep(timeToWait);
}
}
}
}
Backend system/remote call class:
public class RemoteCaller{
Service serviceCaller;
public void remoteCall(String message) {
class RemoteCallable implements Callable<Void> {
String message;
public RemoteCallable( String message)
{
this.message = message;
}
public Void call() throws Exception{
serviceCaller.updateDetails( this.message);
return null;
}
}
RetriableHelper<Void> retriableHelper = new RetriableHelper<Void>(5, 500, new RemoteCallable( message));
try {
retriableHelper.call();
} catch (Exception e) {
throw e;
}
}
}
enter link description here Spring has a retry annotation which servers the purpose
Step 1: Add following dependency to your POM
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
Step 2: Enabling Spring Retry
To enable Spring Retry in an application, we need to add the #EnableRetry annotation to our #Configuration class:
Ex:
#Configuration
#EnableRetry
public class AppConfig { ... }
Step 3: To add retry functionality to methods, #Retryable can be used:
Ex:
#Service
public interface MyService {
#Retryable(
value = { SQLException.class },
maxAttempts = 2,
backoff = #Backoff(delay = 5000))
void retryService(String sql) throws SQLException;
...
}
Step 4.The #Recover annotation is used to define a separate recovery method when a #Retryable method fails with a specified exception:
Ex:
#Service
public interface MyService {
...
#Recover
void recover(SQLException e, String sql);
}
See Url for more details : http://www.baeldung.com/spring-retry
where do you get the services from? use a factory to Proxy the service you get from the original factory. The proxy can then implement the retry transparently. See the java Proxy/ProxyGenerators in reflection.
If you are using spring , then better go with Aspects.
Otherwise, below sample solution can work:
public class Test
{
public static void main(String[] args) throws Exception
{
Test test = new Test();
test.toRunFirst("Hello! This is normal invocation");
runWithRetry(test, "toRunFirst", "Hello! This is First, called with retry");
runWithRetry(test, "toRunSecond", "Hello! This is Second, called with retry");
}
public void toRunFirst(String s) {
System.out.println(s);
}
public void toRunSecond(String s) {
System.out.println(s);
}
public static Object runWithRetry(Object obj, String methodName, Object... args) throws Exception
{
Class<?>[] paramClass = new Class<?>[args.length];
for(int i=0; i< args.length; i++) {
paramClass[i] = args[i].getClass();
}
Method method = obj.getClass().getDeclaredMethod(methodName, paramClass);
int retryCount = 2;
for(int i=0; i< retryCount; i++) {
try {
return method.invoke(obj, args);
}
catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
I did not find what I needed so there is mine.
The main feature is that it throws the type of Exception you need when maxRetries is reached so you can catch it in the call.
import org.apache.log4j.Logger;
public class TaskUtils {
public static <E extends Throwable> void retry(int maxRetries, Task<E> task) throws E {
retry(maxRetries, 0, null, task);
}
public static <E extends Throwable> void retry(int maxRetries, long waitTimeMs, Logger logger, Task<E> task) throws E {
while (maxRetries > 0) {
maxRetries--;
try {
task.run();
} catch (Exception e) {
if (maxRetries == 0) {
try {
throw e;
} catch (Exception ignored) { // can't happen but just in case we wrap it in
throw new RuntimeException(e);
}
}
if (logger != null)
logger.warn("Attempt " + maxRetries + " failed", e);
try {
Thread.sleep(waitTimeMs);
} catch (InterruptedException ignored) {
}
}
}
}
public interface Task<E extends Throwable> {
void run() throws E;
}
}
Usage :
TaskUtils.retry(3, 500, LOGGER, () -> stmClickhouse.execute(
"ALTER TABLE `" + database + "`.`" + table.getName() + "` ON CLUSTER " + clusterName + allColumnsSql
));
add it into pom.xml
<dependency>
<groupId>org.deking.utils</groupId>
<artifactId>retry</artifactId>
<version>0.0.2-SNAPSHOT</version>
</dependency>
new Retry<String>()
.maxOperationWaitTime(30_000)//Max operation wait time during a single operation
.retryIntervalTime(1_000)//Interval time between two operations
.maxRetryTimes(3)//Retry times when operation failed(or timeout) at the first time
.operation(() -> {
//your operation
return "success!";
})
.judgement(t -> (t == null || t.isEmpty()))//add your judgement whether the operation should be retry(Operation should return a value)
.execute();
If you want add retry config annotation on method,and call it:
class RetryTests{
#RetryConfig( maxRetryTimes=1)
public static String TestAnnotation() {
return "aaa";
}
public static void main(String[] args) {
try {
new Retry<String>()
.of(RetryTest.class.getMethod("TestAnnotation"),null)
.judgement(r -> r.equals("aaa"))
.execute();
} catch (NoSuchMethodException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}