Apache Camel Exception Handling + Spring Boot Example
Overview
In this article, we will understand different approaches of handling Exceptions in Apache Camel.
Apache Camel Tutorial :
- Apache Camel Overview and Architecture
- Install ActiveMQ And Start ActiveMQ Server on Windows
- Apache Camel with Spring Boot Simple Example
- Apache Camel Exception Handling Example
- Apache Camel ActiveMQ with Spring Boot Message Example
- Spring Boot + Apache Camel Quartz Example
- Apache Camel Interview Questions and Answers
What is a Apache Camel?
Apache Camel is an open-source integration framework for implementing various Enterprise Integration Patterns(EIPs), that allows you to quickly and easily connect different systems that consume or produce data.
It is easy to use Domain Specific Languages (DSLs) to wire EIPs and transports together
Camel receives messages from some endpoint and sends it to another one. The messages can be processed or simply routed to different endpoint depending on transformation logic applied in Camel.
It allows integration simple by providing connectivity to a wide variety of transports and APIs.
For example, you can easily route JMS to JSON, JSON to JMS, HTTP to JMS, FTP to JMS, even HTTP to HTTP, and connectivity to Microservices.
In this tutorial will take the example from the previous article Apache Camel with Spring Boot Simple Example.
Take a look at our suggested posts:
Project Structure
There are two ways to incorporate exception management in Apache Camel.
- Do Try block
- OnException block
To understand the exception handling in apache camel, will explicitly throw the exception as given below
Create Custom Exception Class to throw exception
package com.techgeeknext.camel.custom.exceptions;
public class GenericCamelCustomException extends RuntimeException {
private static final long serialVersionUID = 7607128449240157466L;
public GenericCamelCustomException(String message){
super(message);
}
}
Then, call above custom exception from Processor Class called ProcessorCustomException, which will get thrown from DSL Route (ActiveMQMsgConsumerRoute).
package com.techgeeknext.camel.custom.exceptions;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
public class ProcessorCustomException implements Processor{
public void process(Exchange exchange) throws Exception {
System.out.println("Custom Exception from ProcessorCustomException.");
throw new GenericCamelCustomException("Custom Exception from ProcessorCustomException.");
}
}
Then, call above custom exception from Processor Class called ProcessorCustomException, which will get thrown from DSL Route (SimpleMessageRoute).
package com.techgeeknext.camel.custom.exceptions;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
public class ProcessorCustomException implements Processor{
public void process(Exchange exchange) throws Exception {
System.out.println("Custom Exception from ProcessorCustomException.");
throw new GenericCamelCustomException("Custom Exception from ProcessorCustomException.");
}
}
package com.techgeeknext.camel.routes;
import com.techgeeknext.camel.custom.exceptions.ProcessorCustomException;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
@Component
public class SimpleMessageRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
//generates an event/constant message every 60 seconds
from("timer:active-mq-timer?period=60000")
.transform().constant("Hello Message from Apache Camel - TechGeekNext")
.log("")
//throw custom exception
.process(new ProcessorCustomException());
//send this message to ActiveMQ queue
// .to("activemq:my-activemq-queue");
}
}
Output: As you can see, it's throwing the exception in the console.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.5)
12:53:00.237 INFO 26336 --- [main] c.t.c.CamelSprintBootServiceAApplication : Starting CamelSprintBootServiceAApplication using Java 12.0.2 on PID 26336 (D:\camel-sprint-boot-service-a\target\classes started in D:\camel-sprint-boot-service-a)
12:53:00.239 INFO 26336 --- [main] c.t.c.CamelSprintBootServiceAApplication : No active profile set, falling back to default profiles: default
12:53:01.431 INFO 26336 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
12:53:01.443 INFO 26336 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
12:53:01.443 INFO 26336 --- [main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.45]
12:53:01.627 INFO 26336 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
12:53:01.627 INFO 26336 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1356 ms
12:53:01.791 INFO 26336 --- [main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
12:53:02.237 INFO 26336 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
12:53:02.381 INFO 26336 --- [main] o.a.c.impl.engine.AbstractCamelContext : Routes startup summary (total:1 started:1)
12:53:02.382 INFO 26336 --- [main] o.a.c.impl.engine.AbstractCamelContext : Started route1 (timer://active-mq-timer)
12:53:02.382 INFO 26336 --- [main] o.a.c.impl.engine.AbstractCamelContext : Apache Camel 3.9.0 (camel-1) started in 153ms (build:32ms init:109ms start:12ms)
12:53:02.384 INFO 26336 --- [main] c.t.c.CamelSprintBootServiceAApplication : Started CamelSprintBootServiceAApplication in 2.462 seconds (JVM running for 2.869)
12:53:03.389 INFO 26336 --- [active-mq-timer] route1 : Hello Message from Apache Camel - TechGeekNext
Custom Exception from ProcessorCustomException.
12:53:03.417 ERROR 26336 --- [active-mq-timer] o.a.c.p.e.DefaultErrorHandler : Failed delivery for (MessageId: 8EFEE4761268FE1-0000000000000000 on ExchangeId: 8EFEE4761268FE1-0000000000000000). Exhausted after delivery attempt: 1 caught:
com.techgeeknext.camel.custom.exceptions.GenericCamelCustomException: Custom Exception from ProcessorCustomException.
at com.techgeeknext.camel.custom.exceptions.ProcessorCustomException.process(ProcessorCustomException.java:10) ~[classes/:na]
at org.apache.camel.support.processor.DelegateSyncProcessor.process(DelegateSyncProcessor.java:66) ~[camel-support-3.9.0.jar:3.9.0]
at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler$SimpleTask.run(RedeliveryErrorHandler.java:439) ~[camel-core-processor-3.9.0.jar:3.9.0]
at org.apache.camel.impl.engine.DefaultReactiveExecutor$Worker.schedule(DefaultReactiveExecutor.java:181) ~[camel-base-engine-3.9.0.jar:3.9.0]
at org.apache.camel.impl.engine.DefaultReactiveExecutor.scheduleMain(DefaultReactiveExecutor.java:62) ~[camel-base-engine-3.9.0.jar:3.9.0]
at org.apache.camel.processor.Pipeline.process(Pipeline.java:167) ~[camel-core-processor-3.9.0.jar:3.9.0]
at org.apache.camel.impl.engine.CamelInternalProcessor.process(CamelInternalProcessor.java:388) ~[camel-base-engine-3.9.0.jar:3.9.0]
at org.apache.camel.component.timer.TimerConsumer.sendTimerExchange(TimerConsumer.java:209) ~[camel-timer-3.9.0.jar:3.9.0]
at org.apache.camel.component.timer.TimerConsumer$1.run(TimerConsumer.java:76) ~[camel-timer-3.9.0.jar:3.9.0]
at java.base/java.util.TimerThread.mainLoop(Timer.java:556) ~[na:na]
at java.base/java.util.TimerThread.run(Timer.java:506) ~[na:na]
Now, let's take a look how to handling exception using below approaches:
Do Try block approach:
This approach is identical to the try catch block in Java. As a result, the thrown exception will be caught instantly, and the message will not keep retrying.
Modified SimpleMessageRoute Route example to use 'do try' approach to handle the exception.
package com.techgeeknext.camel.routes;
import com.techgeeknext.camel.custom.exceptions.GenericCamelCustomException;
import com.techgeeknext.camel.custom.exceptions.ProcessorCustomException;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
@Component
public class SimpleMessageRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
//generates an event/constant message every 60 seconds
from("timer:active-mq-timer?period=60000")
.transform().constant("Hello Message from Apache Camel - TechGeekNext")
.log("")
.doTry().process(new ProcessorCustomException())
.doCatch(GenericCamelCustomException.class).process(new Processor() {
public void process(Exchange exchange) throws Exception {
System.out.println("Handled exception from try catch.");
}
});
}
}
Output: Run the above program, you can see the exception got handled in catch block.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.5)
15:44:46.031 INFO 26188 --- [main] c.t.c.CamelSprintBootServiceAApplication : Starting CamelSprintBootServiceAApplication using Java 12.0.2 on PID 26188 (D:\camel-sprint-boot-service-a\target\classes started in D:\camel-sprint-boot-service-a)
15:44:46.034 INFO 26188 --- [main] c.t.c.CamelSprintBootServiceAApplication : No active profile set, falling back to default profiles: default
15:44:47.179 INFO 26188 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
15:44:47.193 INFO 26188 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
15:44:47.193 INFO 26188 --- [main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.45]
15:44:47.384 INFO 26188 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
15:44:47.384 INFO 26188 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1319 ms
15:44:47.537 INFO 26188 --- [main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
15:44:48.006 INFO 26188 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
15:44:48.194 INFO 26188 --- [main] o.a.c.impl.engine.AbstractCamelContext : Routes startup summary (total:1 started:1)
15:44:48.196 INFO 26188 --- [main] o.a.c.impl.engine.AbstractCamelContext : Started route1 (timer://active-mq-timer)
15:44:48.196 INFO 26188 --- [main] o.a.c.impl.engine.AbstractCamelContext : Apache Camel 3.9.0 (camel-1) started in 199ms (build:36ms init:150ms start:13ms)
15:44:48.201 INFO 26188 --- [main] c.t.c.CamelSprintBootServiceAApplication : Started CamelSprintBootServiceAApplication in 2.495 seconds (JVM running for 2.993)
15:44:49.215 INFO 26188 --- [active-mq-timer] route1 : Hello Message from Apache Camel - TechGeekNext
Custom Exception from ProcessorCustomException.
Handled exception from try catch.
Limitation of using do try Approach
The downside of this approach is that it is only applicable for one route.
Assume we have one more routes says route 1 and route2. If an exception exists in one of the route, the current doTry block can not handle it both route exception.
package com.techgeeknext.camel.routes;
import com.techgeeknext.camel.custom.exceptions.GenericCamelCustomException;
import com.techgeeknext.camel.custom.exceptions.ProcessorCustomException;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
@Component
public class SimpleMessageRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
//generates an event/constant message every 60 seconds
from("timer:active-mq-timer?period=60000")
.transform().constant("Hello Message from Apache Camel - TechGeekNext")
.log("")
.doTry().process(new ProcessorCustomException())
.doCatch(GenericCamelCustomException.class).process(new Processor() {
public void process(Exchange exchange) throws Exception {
System.out.println("Handled exception from try catch.");
}
});
//generates an event/constant message every 60 seconds
from("timer:active-mq-timer?period=60000")
.transform().constant("Hello Message from Apache Camel - TechGeekNext")
.log("")
//throw custom exception
.process(new ProcessorCustomException());
}
}
Output: Run the program, you can notice that route1 exception got handled using do try approach, whereas route2 exception has not been handled and thrown exception.
15:57:59.048 INFO 6972 --- [main] c.t.c.CamelSprintBootServiceAApplication : Started CamelSprintBootServiceAApplication in 2.448 seconds (JVM running for 2.847)
15:58:00.059 INFO 6972 --- [active-mq-timer] route1 : Hello Message from Apache Camel Route1 - TechGeekNext
Custom Exception from ProcessorCustomException.
Handled exception from try catch.
15:58:00.065 INFO 6972 --- [active-mq-timer] route2 : Hello Message from Apache Camel Route2- TechGeekNext
Custom Exception from ProcessorCustomException.
15:58:00.077 ERROR 6972 --- [active-mq-timer] o.a.c.p.e.DefaultErrorHandler : Failed delivery for (MessageId: 7A3E31DBFC5BBC3-0000000000000000 on ExchangeId: 7A3E31DBFC5BBC3-0000000000000000). Exhausted after delivery attempt: 1 caught: com.techgeeknext.camel.custom.exceptions.GenericCamelCustomException: Custom Exception from ProcessorCustomException.
com.techgeeknext.camel.custom.exceptions.GenericCamelCustomException: Custom Exception from ProcessorCustomException.
at com.techgeeknext.camel.custom.exceptions.ProcessorCustomException.process(ProcessorCustomException.java:10) ~[classes/:na]
at org.apache.camel.support.processor.DelegateSyncProcessor.process(DelegateSyncProcessor.java:66) ~[camel-support-3.9.0.jar:3.9.0]
Solution
Solution is to use OnException block approach, so that the exception handling would be applicable to all routes of that class.
OnException block approach
Modify SimpleMessageRoute class to handle exception using OnException block approach.
package com.techgeeknext.camel.routes;
import com.techgeeknext.camel.custom.exceptions.GenericCamelCustomException;
import com.techgeeknext.camel.custom.exceptions.ProcessorCustomException;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
@Component
public class SimpleMessageRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
onException(GenericCamelCustomException.class).process(new Processor() {
public void process(Exchange exchange) throws Exception {
System.out.println("Handled exception using onException block.");
}
}).log("").handled(true);
//generates an event/constant message every 60 seconds
from("timer:active-mq-timer?period=60000")
.transform().constant("Hello Message from Apache Camel Route1 - TechGeekNext")
.log("")
//throw custom exception
.process(new ProcessorCustomException());
//generates an event/constant message every 60 seconds
from("timer:active-mq-timer?period=60000")
.transform().constant("Hello Message from Apache Camel Route2 - TechGeekNext")
.log("")
//throw custom exception
.process(new ProcessorCustomException());
}
}
Output: Run the above program and you can notice exception is getting handled for both Route1 and Route2.
16:09:55.002 INFO 14956 --- [main] c.t.c.CamelSprintBootServiceAApplication : Started CamelSprintBootServiceAApplication in 2.268 seconds (JVM running for 2.71)
16:09:56.007 INFO 14956 --- [active-mq-timer] route1 : Hello Message from Apache Camel Route1 - TechGeekNext
Custom Exception from ProcessorCustomException.
Handled exception using onException block.
16:09:56.026 INFO 14956 --- [active-mq-timer] route1 : Hello Message from Apache Camel Route1 - TechGeekNext
16:09:56.031 INFO 14956 --- [active-mq-timer] route2 : Hello Message from Apache Camel Route2 - TechGeekNext
Custom Exception from ProcessorCustomException.
Handled exception using onException block.
16:09:56.033 INFO 14956 --- [active-mq-timer] route2 : Hello Message from Apache Camel Route2 - TechGeekNext
16:10:56.006 INFO 14956 --- [active-mq-timer] route1 : Hello Message from Apache Camel Route1 - TechGeekNext
Custom Exception from ProcessorCustomException.
Handled exception using onException block.
16:10:56.007 INFO 14956 --- [active-mq-timer] route1 : Hello Message from Apache Camel Route1 - TechGeekNext
16:10:56.038 INFO 14956 --- [active-mq-timer] route2 : Hello Message from Apache Camel Route2 - TechGeekNext
Custom Exception from ProcessorCustomException.
Handled exception using onException block.
Download Source Code
The full source code for this article can be found on below.Download it here - Apache Camel Exception Handling + Spring Boot Example