Plan: API Refactoring for Consistent Exception Handling¶
Status: COMPLETED ✓¶
Completed: 2026-01-21
Commit: 1965fcd - API exception handling refactoring - catch Throwable instead of Exception
All API files now use catch (Throwable th) pattern consistently. No catch (Exception) remains in any API file.
Problem Statement¶
API classes in tqapi module have inconsistent exception handling patterns that result in:
1. Uncaught Error types (e.g., NoClassDefFoundError) propagating to Jetty as HTTP 500
2. Inconsistent response formats - some return Response.Status.INTERNAL_SERVER_ERROR (500)
3. Mixed patterns - some endpoints execute logic directly, others delegate to private methods
4. Some endpoints missing try-catch blocks entirely
Requirements¶
- All classes should be oriented towards
TlinqClientException- business logic errors should be wrapped in or thrown asTlinqClientException - Never return HTTP 500 - always return
Response.ok()(200) with error details inTlinqApiResponse - Always return
TlinqApiResponse- consistent response structure for success and error cases - Follow endpoint pattern - endpoint methods delegate to private
doXxx()methods for actual processing - Catch all Throwables - ensure no exception or error escapes to Jetty
Target Pattern¶
Endpoint Method Pattern¶
@POST
@Path("/action")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response actionEndpoint(Map reqData) {
TlinqApiResponse ar;
String session = null;
try {
session = ApiUtil.gmp(reqData, "session", String.class, false);
logger.info("BEGIN actionEndpoint for " + ApiUtil.sval(session, "N/A"));
ar = doAction(reqData);
} catch (TlinqClientException ex) {
ar = new TlinqApiResponse(ex.getErrorCode(), ex.getMessage());
} catch (Throwable th) {
logger.log(Level.SEVERE, "Error in actionEndpoint", th);
ar = new TlinqApiResponse(TlinqErr.GENERAL,
th.getMessage() == null ? th.getClass().getName() : th.getMessage());
}
logger.info("END actionEndpoint for " + ApiUtil.sval(session, "N/A"));
return Response.ok(ar).build();
}
Private Method Pattern¶
private TlinqApiResponse doAction(Map reqData) throws TlinqClientException {
// Get session, default to system session if not provided
String session = ApiUtil.gmp(reqData, "session", String.class, false);
if (TypeUtil.isEmptyString(session)) {
session = ClientConfig.instance().getDefaultFactory()
.getPropertyList().getProperty("system.session");
}
// Business logic here
SomeFacade facade = new SomeFacade(session);
Object result = facade.doSomething(...);
return new TlinqApiResponse(result);
}
Key Principles¶
- Endpoint method responsibilities:
- Extract session token
- Log BEGIN/END
- Catch
TlinqClientExceptionfirst (preserves error code) - Catch
Throwableas fallback (converts to GENERAL error) -
Always return
Response.ok(ar).build() -
Private
doXxx()method responsibilities: - Declare
throws TlinqClientException - Perform business logic
- Return
TlinqApiResponsedirectly -
May catch and wrap other exceptions as
TlinqClientException -
Never do this:
Response.status(Response.Status.INTERNAL_SERVER_ERROR)- returns 500Response.status(Response.Status.BAD_REQUEST)- returns 400catch (Exception ex)withoutcatch (Throwable th)fallback- Execute complex logic directly in endpoint method
Files Refactored¶
| File | Status | Notes |
|---|---|---|
| BookingApi.java | ✓ Complete | 8 endpoints, 8 Throwable catches |
| UserApi.java | ✓ Complete | 10 endpoints, 10 Throwable catches |
| GroupApi.java | ✓ Complete | 18 endpoints, 19 Throwable catches |
| CommonApi.java | ✓ Complete | 4 endpoints, 7 Throwable catches |
| CruiseApi.java | ✓ Complete | 69 endpoints, 74 Throwable catches (updated 2026-01-21) |
| FlightApi.java | ✓ Complete | 5 endpoints, 10 Throwable catches |
| HotelApi.java | ✓ Complete | 23 endpoints, 47 Throwable catches |
| TripMakerApi.java | ✓ Complete | 34 endpoints, 61 Throwable catches |
| TripOfferApi.java | ✓ Complete | 17 endpoints, 17 Throwable catches |
| CartApi.java | ✓ Complete | 10 endpoints, 16 Throwable catches |
| CustomerApi.java | ✓ Complete | 5 endpoints, 10 Throwable catches |
| DocumentApi.java | ✓ Complete | 16 endpoints, 22 Throwable catches |
| ProductApi.java | ✓ Complete | 15 endpoints, 24 Throwable catches |
| VisaApi.java | ✓ Complete | 18 endpoints, 18 Throwable catches |
| MarketingApi.java | ✓ Complete | 43 endpoints, 39 Throwable catches |
| TiqetsApi.java | ✓ Complete | 14 endpoints, 14 Throwable catches (updated 2026-01-21) |
Detailed Changes Per File¶
Note: All changes below have been completed. This section is kept for historical reference.
1. BookingApi.java ✓ DONE¶
- Added try-catch with
TlinqClientException+Throwableto all endpoints - All 8 endpoints now properly catch Throwable
2. UserApi.java ✓ DONE¶
- Replaced all
Response.status(Response.Status.BAD_REQUEST)patterns - All 10 endpoints now properly catch Throwable
3. GroupApi.java ✓ DONE¶
- Added
catch (Throwable th)to all methods - All 18 endpoints now properly catch Throwable
4. CustomerApi.java ✓ DONE¶
- Removed
Response.Status.INTERNAL_SERVER_ERRORusage - All 5 endpoints now properly catch Throwable
5. TiqetsApi.java ✓ DONE (2026-01-21)¶
- Updated
handleException(String, Exception)tohandleException(String, Throwable) - Changed all 14
catch (Exception ex)tocatch (Throwable th)in public endpoints
6. CruiseApi.java ✓ DONE (2026-01-21)¶
- Changed 2
catch (Exception ex)tocatch (Throwable th)in private helper methods: doCruiseList()doCruiseWrite()
7. All Other API Files ✓ DONE¶
All remaining files (CommonApi, FlightApi, HotelApi, TripMakerApi, TripOfferApi, CartApi, DocumentApi, ProductApi, VisaApi, MarketingApi) were refactored in earlier work.
Search Patterns for Verification¶
After implementation, verify with these searches:
# Should return 0 results:
grep -r "catch (Exception" tqapi/src/main/java/com/perun/tlinq/api/*Api.java
# Should return 0 results:
grep -r "Response.Status.INTERNAL_SERVER_ERROR" tqapi/src/main/java/com/perun/tlinq/api/
grep -r "Response.Status.BAD_REQUEST" tqapi/src/main/java/com/perun/tlinq/api/
# Should find catches in all endpoint methods:
grep -r "catch (Throwable" tqapi/src/main/java/com/perun/tlinq/api/*Api.java
Verification Checklist¶
- [x] No
catch (Exceptionwithoutcatch (Throwablefallback - [x] No
Response.Status.INTERNAL_SERVER_ERROR(500 responses) - [x] No
Response.Status.BAD_REQUEST(400 responses) - [x] All endpoint methods have try-catch blocks
- [x] All endpoint methods delegate to private
doXxx()methods - [x] All catch blocks log with stack trace
- [x] All responses use
Response.ok(ar).build() - [x] Build passes:
./gradlew :tqapi:build - [ ] Tests pass:
./gradlew :tqapi:test(tests skipped by default)
Summary Statistics¶
| Metric | Before | After |
|---|---|---|
catch (Exception ex) occurrences |
16 | 0 |
catch (Throwable th) occurrences |
380 | 396 |
| Endpoints returning 400 | 0 | 0 |
| Endpoints returning 500 | 0 | 0 |
| Endpoints without try-catch | 0 | 0 |
| Total API files | 16 | 16 |
| Total endpoints | ~300 | ~300 |
Note: Most refactoring was completed in earlier work. The final cleanup on 2026-01-21 addressed:
- TiqetsApi.java: 14 catch (Exception) → catch (Throwable) + updated handleException() method
- CruiseApi.java: 2 catch (Exception) → catch (Throwable) in private helper methods