「例外と早期 return の組み合わせテンプレート(業務クラス構造つき)」は、
実務で“安全で読みやすくメンテしやすい”コードを書くための基本骨格です。
ここでは、3層構造(Controller/Service/Repository)に沿って、
「早期return」と「例外処理」をどう共存させるかを図とコードで解説します。
1. 全体アーキテクチャ図
┌────────────────────────────┐
│ Controller:入力と結果の橋渡し │
│ ├─ バリデーション → 早期return │
│ ├─ 想定外エラー → 例外catch │
│ └─ レスポンスを返す │
├────────────────────────────┤
│ Service:業務ロジック中心 │
│ ├─ 不正データ → 業務例外throw │
│ ├─ DBエラー → システム例外throw │
│ └─ 正常ならreturn │
├────────────────────────────┤
│ Repository:DBアクセス層 │
│ └─ SQLExceptionを捕捉→変換throw │
└────────────────────────────┘
2. クラス構造イメージ
com.example.app
├─ controller
│ └─ UserController.java
├─ service
│ └─ UserService.java
├─ repository
│ └─ UserRepository.java
└─ exception
├─ ValidationException.java
├─ BusinessException.java
└─ SystemException.java
3. コントローラー層(早期return中心)
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
private final Logger log = LoggerFactory.getLogger(getClass());
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody User user) {
// --- ① 早期returnで入力チェック ---
if (user == null || user.getName() == null || user.getName().isEmpty()) {
return ResponseEntity.badRequest().body("名前を入力してください");
}
try {
// --- ② 正常処理 ---
userService.register(user);
return ResponseEntity.ok("登録完了");
} catch (ValidationException e) {
// --- ③ 想定内(業務例外) ---
log.warn("バリデーションエラー: {}", e.getMessage());
return ResponseEntity.badRequest().body(e.getMessage());
} catch (SystemException e) {
// --- ④ 想定外(システム障害) ---
log.error("システムエラー", e);
return ResponseEntity.internalServerError().body("内部エラーが発生しました");
}
}
}
Java🟢 ポイント:
- 「入力不備」は例外ではなく 早期return
- 「業務ロジック上の不正」は 例外でthrow
- Controllerは「レスポンスに変換」する役割に専念
4. サービス層(業務例外・システム例外)
@Service
public class UserService {
private final UserRepository userRepository;
public void register(User user) {
// --- ① 業務チェック ---
if (userRepository.existsByName(user.getName())) {
throw new ValidationException("同じ名前のユーザーが既に存在します");
}
try {
// --- ② データ登録 ---
userRepository.insert(user);
} catch (SQLException e) {
// --- ③ システム例外に変換して上位へ ---
throw new SystemException("DB登録に失敗しました", e);
}
}
}
Java🟡 ポイント:
- 業務ロジック違反は
ValidationException(想定内) - システム障害は
SystemException(想定外) - 呼び出し元(Controller)がキャッチして処理を分ける
5. リポジトリ層(低レベル例外を変換)
@Repository
public class UserRepository {
private final JdbcTemplate jdbcTemplate;
public void insert(User user) throws SQLException {
jdbcTemplate.update(
"INSERT INTO users (name, email) VALUES (?, ?)",
user.getName(), user.getEmail()
);
}
public boolean existsByName(String name) {
String sql = "SELECT COUNT(*) FROM users WHERE name = ?";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class, name);
return count != null && count > 0;
}
}
Java⚙️ ポイント:
- DBアクセスは例外を上位へスロー(Serviceでキャッチして再throw)
- Repositoryは「例外を包んで投げる」層ではなく「実行担当」
6. 例外クラス設計例
// 入力や業務ルール違反
public class ValidationException extends RuntimeException {
public ValidationException(String message) { super(message); }
}
// ビジネスロジック全般の例外
public class BusinessException extends RuntimeException {
public BusinessException(String message) { super(message); }
}
// システム障害(DB, IO等)
public class SystemException extends RuntimeException {
public SystemException(String message, Throwable cause) { super(message, cause); }
}
Java💡 ポイント
- 意味のある例外名で分類する(何が想定内・外か明確化)
- RuntimeExceptionを継承して煩雑なthrows宣言を省略
7. ベストプラクティスまとめ表
| 層 | 想定内エラー | 想定外エラー | return / throw 方針 |
|---|---|---|---|
| Controller | 入力ミス・空値など | – | return(早期) |
| Service | 業務ロジック違反 | DB障害など | throw |
| Repository | – | SQL例外など | throw(SystemException) |
8. 図で理解:return と throw の流れ
Controller
├─ 入力NG → return (400)
└─ 正常入力 → Service呼出
├─ 業務NG → throw ValidationException → Controllerで400
├─ DB障害 → throw SystemException → Controllerで500
└─ 正常 → return OK
9. 実務テンプレート(雛形まとめ)
if (入力NG) return 400;
try {
service.処理();
return 200;
} catch (ValidationException e) {
return 400;
} catch (SystemException e) {
return 500;
}
Java→ 「早期return」+「例外伝播」の明確分離が安全設計の基本。


