apache-camelspring-camel

Is Spring Boot Camel onException handling Broken after 4.7.x release?


We are using camel as ESB and noticed something breaking after 4.7.x release.

The "onException" registered in routes are not working. Control won't flow into this block but works fine in versions prior to 4.7.x. Was there any breaking api change introduced in this version?

I have attached the very small boot gradle project to replicate the scenario. In build.gradle just switch the camel version and you can see the difference.

If you use version prior version to 4.7, everything works as excepted. The below code will print "exception caught!!" and returns a 200 OK response. But if you upgrade to 4.7.x or later, the control won't reach in to the onException block and rest response returns a 500 response.

URL : http://localhost:8080/camel/demo

@Component
public class TestRoute extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        onException(Throwable.class)
                .process(new Processor() {
                    public void process(Exchange exchange) throws Exception {
                        System.out.println("exception caught!!");
                    }
                }).handled(true);
        rest("/")
            .consumes("application/ json;charset=UTF-8")
            .produces("application/ json;charset=UTF-8")
            .get("/demo")
            .to("direct:next-step");
        from("direct:next-step")
            .doTry()
                // invoking SOAP service
                .process(exchange -> {
                    throw new RuntimeException("Random Exception");
                })
            .doCatch(Throwable.class)
                .process(exchange -> {
                    throw new IllegalStateException("not in a valid state");
                });
    }
}

My build.gradle file below :

buildscript {
    ext {
        camelSpringVersion = '4.4.5' // this works as expected
        //camelSpringVersion = '4.10.2' // this is broken
    }
}
plugins {
    id 'java'
    id 'war'
    id 'org.springframework.boot' version '3.4.3'
    id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    implementation "org.apache.camel:camel-http:${camelSpringVersion}"
    implementation "org.apache.camel.springboot:camel-spring-boot-starter:${camelSpringVersion}"
    implementation "org.apache.camel.springboot:camel-servlet-starter:${camelSpringVersion}"
    implementation "org.apache.camel.springboot:camel-jackson-starter:${camelSpringVersion}"
    implementation "org.apache.camel:camel-health:${camelSpringVersion}"
}

tasks.named('test') {
    useJUnitPlatform()
}

Solution

  • Solution 1 ::

    In order to preserve existing behaviour, you need to set this property

    camel.rest.inline-routes = false 
    

    in all versions post 4.7.x.

    Solution 2 ::

    If you add a dummy route, its working as expected. For example if you modify above code as below, its working as expected.

    @Component
    public class TestRoute extends RouteBuilder {
    
        @Override
        public void configure() throws Exception {
    
            onException(Throwable.class)
                    .process(new Processor() {
                        public void process(Exchange exchange) throws Exception {
                            System.out.println("exception caught!!");
                        }
                    }).handled(true);
    
            rest("/")
                .consumes("application/ json;charset=UTF-8")
                .produces("application/ json;charset=UTF-8")
                .get("/demo")
                .to("direct:next-step-dummy");
    
       from("direct:next-step-dummy") // setting a dummy route
                .to("direct:next-step");
    
            from("direct:next-step")
                .doTry()
                    // invoking SOAP service
                    .process(exchange -> {
                        throw new RuntimeException("Random Exception");
                    })
                .doCatch(Throwable.class)
                    .process(exchange -> {
                        throw new IllegalStateException("not in a valid state");
                    });
        }
    }