Skip to content

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

  1. All classes should be oriented towards TlinqClientException - business logic errors should be wrapped in or thrown as TlinqClientException
  2. Never return HTTP 500 - always return Response.ok() (200) with error details in TlinqApiResponse
  3. Always return TlinqApiResponse - consistent response structure for success and error cases
  4. Follow endpoint pattern - endpoint methods delegate to private doXxx() methods for actual processing
  5. 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

  1. Endpoint method responsibilities:
  2. Extract session token
  3. Log BEGIN/END
  4. Catch TlinqClientException first (preserves error code)
  5. Catch Throwable as fallback (converts to GENERAL error)
  6. Always return Response.ok(ar).build()

  7. Private doXxx() method responsibilities:

  8. Declare throws TlinqClientException
  9. Perform business logic
  10. Return TlinqApiResponse directly
  11. May catch and wrap other exceptions as TlinqClientException

  12. Never do this:

  13. Response.status(Response.Status.INTERNAL_SERVER_ERROR) - returns 500
  14. Response.status(Response.Status.BAD_REQUEST) - returns 400
  15. catch (Exception ex) without catch (Throwable th) fallback
  16. 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 + Throwable to 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_ERROR usage
  • All 5 endpoints now properly catch Throwable

5. TiqetsApi.java ✓ DONE (2026-01-21)

  • Updated handleException(String, Exception) to handleException(String, Throwable)
  • Changed all 14 catch (Exception ex) to catch (Throwable th) in public endpoints

6. CruiseApi.java ✓ DONE (2026-01-21)

  • Changed 2 catch (Exception ex) to catch (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 (Exception without catch (Throwable fallback
  • [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