How to create an Exception with multiple messages? - java

I'm getting response from external service that looks like:
"status": {
"httpCode": "external service response code, e.g. 201",
"errors": [
{
"code": "error code, e.g. 13",
"message": "e.g. Invalid value for some variable"
},
{
"code": "12",
"message": "Invalid phone number format"
}
]
}
The errors list may have multiple objects. While returning the response from that external service to my frontend application, I'd like to show all the messages. How do I do that? As far as I know Exception related classes only have a single field called message.

Exceptions are mostly not special, they are just a type definition same as any other. You can have them do whatever you want, as long as they extend Throwable. For example:
public class MaciazServiceException extends Exception {
private final Map<Integer, String> codeToMessageMapping;
public MaciazServiceException(JSON json) {
// code here that pulls code and message apart and makes...
Map<Integer, String> codeToMessageMapping = ....;
this.codeToMessageMapping = codeToMessageMapping;
}
#Override public String getMessage() {
// code here that returns a nice view of the above. For example...
return codeToMessageMapping.entrySet().stream().map(
entry -> entry.getKey() + " = " + entry.getValue())
.collect(Collectors.joining("\n"));
}
// you can define methods too, if you want:
public boolean hasErrorCode(int code) {
return codeToMessageMapping.containsKey(code);
}
}
This can then be used elsewhere:
try {
myMaciazService.doThingie(...);
} catch (MaciazServiceException e) {
if (e.hasErrorCode(MaciazService.ERRORCODE_NOT_AUTHORIZED)) {
userpassView.show();
}
}

One of the way to customize the Http response for exceptions, by using the Custom Exceptions.
1.Create a custom exception by using RuntimeException Class
public class SampleException extends RuntimeException {
private List<SampleNestedObject> messages;
public SampleException() {
}
public SampleException(List<SampleNestedObject> messages){
this.messages=messages;
}
}
public class SampleNestedObject {
private int httpCode;
private String message;
//Getters,Setters,Contructors
}
2.Create a response Structure
public class SampleErrorResponse {
private List<SampleNestedObject> messages;
//Getters,Setters,Contructors
}
Create an ExceptionHandler
#ExceptionHandler(SampleException.class)
public final ResponseEntity<Object> handleSampleException(SampleException ex,WebRequest request){
SampleErrorResponse errorResponse=new SampleErrorResponse(ex.getMessages());
return new ResponseEntity(errorResponse,HttpStatus.NOT_FOUND); //Any status code
}
4.Throw exception whenever you want to.
#GetMapping("/getException")
public ResponseEntity<Object> getException(){
List<SampleNestedObject> messages= Arrays.asList(new SampleNestedObject(404,"Sample Message 1"),new SampleNestedObject(404,"Sample Message 2"));
throw new SampleException(messages);
}
Response for the above sample will be,
{
"messages": [
{
"httpCode": 404,
"message": "Sample Message 1"
},
{
"httpCode": 404,
"message": "Sample Message 2"
}
]
}
Hope this will work.

Related

#RestControllerAdvice not working in Spring Boot reactive java application

I am currently throwing a custom Exception - RequestValidationException.
ExceptionHandler class:
#RestControllerAdvice
#Slf4j
public class RestExceptionHandler {
#ExceptionHandler(value = RequestValidationException.class)
#ResponseStatus(HttpStatus.PRECONDITION_FAILED)
public Mono<HttpValidationError> handleRequestValidationException(RequestValidationException exception) {
log.error("Received exception: ", exception);
List<String> loc = new ArrayList<>();
loc.add(exception.getMessage());
ValidationError validationError = ValidationError.builder()
.loc(loc)
.msg(exception.getMessage())
.build();
List<ValidationError> errorMessages = new ArrayList<>();
errorMessages.add(validationError);
return Mono.just(HttpValidationError.builder().detail(errorMessages).build());
}
RequestValidationException class:
public class RequestValidationException extends RuntimeException {
public static final HttpStatus statusCode = HttpStatus.PRECONDITION_FAILED;
public RequestValidationException(String text) {
super(text);
}
public HttpStatus getStatusCode() {
return statusCode;
}
}
When the exception is thrown, I want the following response:
Code: 412
{
"detail": [
{
"loc": [
"No ID found to update. Please add an ID"
],
"msg": "No ID found to update. Please add an ID",
"type": null
}
]
}
What I am receiving is:
{
"error_code": 500,
"message": "No ID found to update. Please add an ID"
}
I checked the application logs and nowhere is the RestExceptionHandler being called. It just logs this error:
"level":"ERROR","logger":"c.a.c.c.c.AbstractController","thread":"boundedElastic-1","message":"Controller exception","stack":"<#384d845f> c.a.c.a.e.RequestValidationException
I just can't seem to figure out what's wrong with this code. Can someone point out what I might be missing? Thanks.
I was only able to get this to work with an implementation of AbstractErrorWebExceptionHandler as follows (sorry for the kotlin code):
#Component
#Order(-2)
class GlobalExceptionHandler(errorAttributes: ErrorAttributes,
resources: WebProperties.Resources,
applicationContext: ApplicationContext,
serverCodecConfigurer: ServerCodecConfigurer) : AbstractErrorWebExceptionHandler(errorAttributes, resources, applicationContext) {
companion object {
private val logger = KotlinLogging.logger {}
private const val HTTP_STATUS_KEY = "status"
private const val MESSAGE_KEY = "message"
private const val ERRORS_KEY = "errors"
}
init {
setMessageWriters(serverCodecConfigurer.writers)
}
override fun setMessageWriters(messageWriters: MutableList<HttpMessageWriter<*>>?) {
super.setMessageWriters(messageWriters)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes?): RouterFunction<ServerResponse> {
return RouterFunctions.route({ true }) { request ->
val error: Throwable = getError(request)
logger.error("Handling: ", error)
val errorProperties = getErrorAttributes(request, ErrorAttributeOptions.defaults())
when (error) {
is WebExchangeBindException -> {
....
}
else -> {
...
}
}
ServerResponse.status(HttpStatus.valueOf(errorProperties[HTTP_STATUS_KEY] as Int))
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(errorProperties)
}
}
}
In Java it would be something like:
#Component
#Order(-2)
public class GlobalExceptionHandler extends AbstractErrorWebExceptionHandler {
private static final String HTTP_STATUS_KEY = "status";
private static final String MESSAGE_KEY = "message";
private static final String ERRORS_KEY = "errors";
public GlobalExceptionHandler(ErrorAttributes errorAttributes, Resources resources, ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer) {
super(errorAttributes, resources, applicationContext);
this.setMessageWriters(serverCodecConfigurer.getWriters());
}
public final void setMessageWriters(List messageWriters) {
super.setMessageWriters(messageWriters);
}
protected RouterFunction getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> errorPropertiesMap = getErrorAttributes(request,
ErrorAttributeOptions.defaults());
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(errorPropertiesMap));
}
}
You can check more details at https://www.baeldung.com/spring-webflux-errors#global.
I made a very trivial mistake of extending the controller with AbstractController class which was causing this issue. Removing it solved my problem.

ERROR - Bad request; check the error message for details

I created API with spring. When I call that API then show error
Here is the Controller class.
#RequestMapping(value={"/dPIUsagePackageInfo"},method=RequestMethod.POST)
public ResponseEntity<DPIUsagePackageInfoRs> dPIUsagePackageInfo(#RequestBody List<DPIUsagePackageInfoRq> dPIUsagePackageInfoRq){
//
DPIUsagePackageInfoRs response = this.ccbsBusiness.dPIUsagePackageInfo(dPIUsagePackageInfoRq);
return new ResponseEntity(response, response.getStatus());
}
Here is the Request class
public class DPIUsagePackageInfoRq {
private List<String> srvName;
public List<String> getSrvName() {
return srvName;
}
public void setSrvName(List<String> srvName) {
this.srvName = srvName;
}
}
I passed this json body
{
"dPIUsagePackageInfoRq" : {
"srvName": ["xxx","rrr","rrrrr"]
}
}
But response like this
{
"resultCode": "000400",
"resultDesc": "ERROR - Bad request; check the error message for details."
}
Where is the wrong with my code.Thanks in advanced.
You are sending only one DPIUsagePackageInfoRq instance where you should be sending it in a list. Please try adding "[" and "]" to start and end of your body so that it becomes a list.
The request which you are sending should be as follows:
[
{
"srvName": ["xxx","rrr","rrrrr"]
}
]
and if you would like to send multiple DPIUsagePackageInfoRq objects, you can use increment the objects like this:
[
{
"srvName": ["xxx","rrr","rrrrr"]
},
{
"srvName": ["xxx","rrr","rrrtrr"]
}
]

How to return Array of two JSON Object in Jersey RESTFul API(JAX-RS)

I have designed login module in RESTFul API using jersey.
whenever any error occurred while login it will return error code and message like,
{
"errorFlag": 1,
"errorMessage": "Login Failed"
}
but whenever I get successful results it returns
{
"apiKey": "3942328b-fa65-496c-bf32-910aafbc1b0e",
"email": "caXXXX#gmail.inl",
"name": "Chandrakant"
}
I'm looking for results like below
{
"errorFlag": 0,
"errorMessage":{
"apiKey": "3942328b-fa65-496c-bf32-910aafbc1b0e",
"email": "caXXXX#gmail.inl",
"name": "Chandrakant"}
}
Use structure like below,
{
status/statusCode : 200/400, //eg. 200 for success, any other for failure.
statusMessage : "Success/<failureMessage>",
errorDetails : "Failed due to <reason>" //optional
data :{ //data will exists only in case of success call.
}
}
you can achieve this like below,
#GET
#Path("/images/{image}")
#Produces("image/*")
public Response getImage(#PathParam("image") String image) {
  File f = new File(image);
 
  if (!f.exists()) {
    throw new WebApplicationException(404);
  }
 
  String mt = new MimetypesFileTypeMap().getContentType(f);
  return Response.ok(f, mt).build();
}
You can return all the attributes in HashMap as key value .
Below piece of code worked for me
#POST
#Path("/test")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public HashMap check(InputStream inputJsonObj) {
HashMap map = new HashMap();
map.put("key1", "value1");
return map;
}

return any exception in json in rest api

Is there any simple methods to return exception in JSON using Rest api?
I've already googled this question, but all solutions i see, was about throwing exceptions during some calculations. But what if income parameters are wrong? I mean what if there is sone string instead of int input parameter?
I created some DTO class for input data:
#XmlRootElement
public class RequestDTO implements Serializable{
private static final long serialVersionUID = 1L;
#XmlElement(name = "request_id")
private String requestId;
#XmlElement(name = "site")
private List<String> sitesIds;
#XmlElement(name = "date_begin")
#JsonSerialize(using = DateSerializer.class)
#JsonDeserialize(using = DateDeserializer.class)
private Date dateBegin;
#XmlElement(name = "date_end")
#JsonSerialize(using = JsonDateSerializer.class)
#JsonDeserialize(using = JsonDateDeserializer.class)
private Date dateEnd;
#XmlElement(name = "volume")
private double volume;
// there is getters and setters
}
If i sent something like 'qwerty' instead of 'volume' field in my json request i'l see error message like Runtime. Is it possible to handle it in someway? I mean to return error in json with such structure?
public class ExceptionDTO {
private String shortExceptionMessage;
private String stackTrace;
public ExceptionDTO(String shotExceptionMessage, String stackTrace){
this.shortExceptionMessage = shotExceptionMessage;
this.stackTrace = stackTrace;
}
public String getShortExceptionMessage() {
return shortExceptionMessage;
}
public String getStackTrace() {
return stackTrace;
}
}
UPD1:
#Provider
#Singleton
public class ExceptionMapperProvider implements ExceptionMapper<Exception>{
#Override
public Response toResponse(final Exception e) {
StringBuilder trace = new StringBuilder();
IntStream.range(0, e.getStackTrace().length)
.forEach(i -> trace.append(e.getStackTrace()[i]).append('\n'));
ExceptionDTO exceptionMessage = new ExceptionDTO(
e.toString(),
trace.toString()
);
return Response.status(500).entity(exceptionMessage).build();
}
}
As it's not really clear if you are interested on checking if field or value of the payload is correct, here are a few ways to work with both.
If you want to check if the value for a field is correct (ie volume field value should be greater than zero etc), check out bean validation. This makes use of annotations on the fields you want to verify.
// for example
#Min(value = 0, message = "invalid message")
private double range;
To use your ExceptionDTO as error response whenever one of those validation fails, you can do so by creating an ExceptionMapper<ConstraintViolationException>. check it here for more details.
If you are checking for the invalid field (ie client sends ragne fields instead of range), have a look at the stack trace on what exception is being thrown. Then register an exception mapper with your ExceptionDTO as body.
For example, if UnrecognizedPropertyException is thrown then you can add:
#Provider
public class UnrecognizedPropertyExceptionMapper implements ExceptionMapper<UnrecognizedPropertyException> {
#Override
public Response toResponse(UnrecognizedPropertyException e) {
ExceptionDTO myDTO = // build response
return Response.status(BAD_REQUEST).entity(myDTO).build();
}
}
If you want to validate input parameters in the request, you should return status code 400 (Bad Request) along with the error details. You can simply send json
{ "error": { "message": "string received for parameter x, where as int expected" } with the response status code 400.
`
I did a bit of research and determined that the best way to encode a Java exception in JSON is to use a convention developed by Oasis that looks like this:
{
"error": {
"code": "400",
"message": "main error message here",
"target": "approx what the error came from",
"details": [
{
"code": "23-098a",
"message": "Disk drive has frozen up again. It needs to be replaced",
"target": "not sure what the target is"
}
],
"innererror": {
"trace": [ ... ],
"context": [ ... ]
}
}
}
details is a list that should have an entry for each nested cause exception in the chain.
innererror.trace should include the stack trace if you wish, as a list of string values.
The response status code should be 400 unless you have a good reason for making it something else, and the code in the structure should match whatever you sent.
Write one method to convert a Java exception to this format, and you are done. Use it consistently and your JS code will be able to handle and display the exception values.
More of the details of the other approaches evaluated and dismissed are covered in this blog post on JSON REST API – Exception Handling
https://agiletribe.purplehillsbooks.com/2015/09/16/json-rest-api-exception-handling/
Here is the java method to convert an exception to this format:
public static JSONObject convertToJSON(Exception e, String context) throws Exception {
JSONObject responseBody = new JSONObject();
JSONObject errorTag = new JSONObject();
responseBody.put("error", errorTag);
errorTag.put("code", 400);
errorTag.put("target", context);
JSONArray detailList = new JSONArray();
errorTag.put("details", detailList);
String lastMessage = "";
Throwable runner = e;
while (runner!=null) {
String className = runner.getClass().getName();
String msg = runner.toString();
runner = runner.getCause();
JSONObject detailObj = new JSONObject();
detailObj.put("message",msg);
int dotPos = className.lastIndexOf(".");
if (dotPos>0) {
className = className.substring(dotPos+1);
}
detailObj.put("code",className);
System.out.println(" ERR: "+msg);
detailList.put(detailObj);
}
JSONObject innerError = new JSONObject();
errorTag.put("innerError", innerError);
JSONArray stackList = new JSONArray();
runner = e;
while (runner != null) {
for (StackTraceElement ste : runner.getStackTrace()) {
String line = ste.getFileName() + ":" + ste.getMethodName() + ":" + ste.getLineNumber();
stackList.put(line);
}
stackList.put("----------------");
runner = runner.getCause();
}
errorTag.put("stack", stackList);
return responseBody;
}

Spring sends 400 bad response when List is initialized outside default constructor, works when initialized inside it or not initialized at all

this is the JSON I am sending:
{
"code": "400173258",
"name": "APPLE pie 2",
"onlinePrice": "800",
"mrp": "1000",
"validity": "123",
"videoConsultCount": "11",
"audioConsultCount": "11",
"textConsultCount": "11",
"audioVideoDuration": "15",
"textConsultValidityDays": 0,
"medicineIncluded": true,
"medicalTestDetail": null,
"description": "wOW",
"showOnWebsite": true,
"type": "MC"
}
This is my controller:
#RequestMapping("/edit")
#ResponseBody
public HealthPackageCRUDResponse editHealthPackage(#RequestBody AddEditHealthPackageForm healthPackageForm) {
HealthPackageCRUDResponse response = new HealthPackageCRUDResponse();
LybrateUser user = WebContextUtils.getCurrentUser();
if (user == null) {
LOG.info("user is not logged in.");
response.setSuccessful(false);
response.setMessage("Please login to create package.");
return response;
}
if (StringUtils.isEmpty(healthPackageForm.getCode())) {
LOG.info("Health package code is missing");
response.setSuccessful(false);
response.setMessage("There is no such package.");
return response;
}
HealthPackage healthPackage = healthPackageService.getHealthPackageByCode(healthPackageForm.getCode());
if (healthPackage != null && !HealthPackageStatus.DELETED.code().equalsIgnoreCase(healthPackage.getStatus()) && healthPackage.isEditable()) {
if (!healthPackage.getUser().getId().equals(user.getUser().getId())) {
response.setSuccessful(false);
response.setMessage("You are not authorized to edit this package.");
return response;
}
healthPackage = healthPackageService.editHealthPackage(healthPackage, healthPackageForm, user.getUser(), "Web", true, HealthPackgeType.MULTI_CONSULT_PACKAGE.code());
response.setSuccessful(true);
response.setMessage("Package Edited.");
response.setHealthPackage(healthPackageConverterService.healthPackageToHealthPackageDTO(healthPackage));
} else {
response.setSuccessful(false);
response.setMessage("Not able to create package");
}
return response;
}
This is the RequestBody mapping object which works:
public class AddEditHealthPackageForm implements Serializable {
private List<HealthPackageMediaDTO> mediaDTOs;
public AddEditHealthPackageForm(){
this.mediaDTOs = new ArrayList<HealthPackageMediaDTO>();
}
And this is the version that doesn't work:
(It works if I add "mediaDTOs": [] to the json object)
public class AddEditHealthPackageForm implements Serializable {
private List<HealthPackageMediaDTO> mediaDTOs = new ArrayList<HealthPackageMediaDTO>(); // this is initialized outside the default constructor
public AddEditHealthPackageForm(){
// this is empty so it doesn't work (gives 400 Bad request syntactically incorrect request)
}
Why is this happening? Shouldn't the spring framework simply assign a null value to mediaDTOs field when it doesn't find it in the json. Why does it work when I initialize the list inside the default constructor. It also works when I don't initialize it at all. It also works if I add "mediaDTOs": [] to the json object. I can't understand why such a basic thing needs to be so confusing? Am I making a silly mistake somewhere?

Categories