Apache Camel × Spring Boot(XML DSL)で onException + handled を使った例外処理まとめ

![81FA51BD-3EC8-4EEA-B238-793305DCF245.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4063686/373b9a25-778e-4f19-8dca-6ec9bac33be5.png)


Apache Camel を使って Spring Boot 上でルーティングを構築していると、例外処理の設計が非常に重要になります。  
特に Camel の `<onException>` と `<handled>` の使い方を正しく理解することで、ルートの安定性や柔軟性が大きく向上します。

この記事では、基本構文から `split` 処理内の例外ハンドリング、そしてユースケース別の使い分けパターンまでを網羅的に解説します。

---

## 使用バージョン

- Apache Camel 4.x  
- Spring Boot 3.x  
- DSL：XML DSL

---

## 基本構文（onException + handled）

```xml
<onException>
    <exception>java.lang.Exception</exception>
    <handled>
        <constant>true</constant>
    </handled>
    <log message="例外をハンドリングしました"/>
    <to uri="mock:error"/>
</onException>
```

---

## handled の true / false の違い

| 設定値         | 動作の意味                                  | Camel の挙動                         | ルートの継続 |
|----------------|----------------------------------------------|--------------------------------------|----------------|
| `<constant>true</constant>`  | 例外を処理済みにする                        | 上位に例外をスローしない             | 継続する or 終了（明示的制御） |
| `<constant>false</constant>` | 例外を未処理として扱う → 再スローする       | Camel が例外として再スローする       | 中断される     |

- `true`：ログを出して終了したり、エラールートに流したりするのに最適  
- `false`：例外を上位に伝播させたいときに使用（テストや REST API 連携など）

---

## サンプルルート：例外を補足して別ルートへ

```xml
<routes xmlns="http://camel.apache.org/schema/spring">

    <!-- 特定の例外をキャッチして処理済みにする -->
    <onException>
        <exception>java.lang.IllegalArgumentException</exception>
        <handled>
            <constant>true</constant>
        </handled>
        <log message="IllegalArgumentException をハンドリング"/>
        <to uri="mock:handled"/>
    </onException>

    <route id="mainRoute">
        <from uri="direct:start"/>
        <process>
            <bean ref="throwingBean"/>
        </process>
        <to uri="mock:result"/>
    </route>

</routes>
```

```java
@Component("throwingBean")
public class ThrowingBean implements Processor {
    @Override
    public void process(Exchange exchange) {
        throw new IllegalArgumentException("テスト例外");
    }
}
```

---

## 応用：条件付きで handled にする

```xml
<handled>
    <simple>${exception.message} contains '非致命'</simple>
</handled>
```

---

## 応用：split の中で例外をキャッチしつつ処理を継続する

```xml
<route id="splitRoute">
    <from uri="direct:start"/>

    <split>
        <simple>${body}</simple>
        <parallelProcessing>false</parallelProcessing>

        <onException>
            <exception>java.lang.Exception</exception>
            <handled>
                <constant>true</constant>
            </handled>
            <log message="スプリット要素で例外を補足しスキップ"/>
            <to uri="mock:error"/>
        </onException>

        <process ref="itemProcessor"/>
        <to uri="mock:each"/>
    </split>

    <to uri="mock:done"/>
</route>
```

```java
@Component("itemProcessor")
public class ItemProcessor implements Processor {
    @Override
    public void process(Exchange exchange) {
        String item = exchange.getIn().getBody(String.class);
        if ("NG".equals(item)) {
            throw new RuntimeException("NG項目が含まれています");
        }
    }
}
```

---

## 【用途別】Camel の `<handled>` の使い分け早見表

| ユースケース | handled の値 | 理由・意図 | 備考 |
|--------------|----------------|-------------|------|
| **バッチ処理中の一部エラーをスキップ** | `true` | エラーを処理済みにし、次のメッセージに進みたい | `split` 内での定型パターン |
| **Kafka/S3送信失敗時に再送処理に回す** | `true` | エラーを補足して「再送」ルートへ渡す | 再送トピックや `deadLetterChannel` へ分岐 |
| **REST API で 500 を返す** | `false` | 例外を外部（Spring Boot）へ伝播し、HTTPエラーを返すため | Spring MVC / WebFlux との連携想定 |
| **テストで例外を検知して失敗させたい** | `false` | Camel テスト内で例外を catch せず、JUnit で検証したい | `mockEndpoint.assertIsSatisfied()` 失敗を狙うケースなど |
| **ログだけ出して処理を終了したい（無視はしない）** | `true` | ユーザー通知や監視は必要だが、処理は止めたい | `log + stop` や `to("mock:alert")` で終了 |
| **一部の例外だけスキップ（条件付き）** | 条件付き `handled` | メッセージ内容や例外内容で柔軟に制御 | `<simple>${exception.message} contains '～'</simple>` |

---

## おわりに

Camel の例外処理は「どこで止めるか・伝播させるか」を設計でしっかり決めることが重要です。  
`<handled>` を正しく使い分けることで、堅牢かつ柔軟なルート構成が可能になります。