Date: 2026-02-23 Scope:
/root/code/Digit-Coreand/root/code/digit-2.9lts-core-stormTotal Instances Found: 40+
| Category | Count | Severity |
|---|---|---|
| Empty catch blocks | 2 | CRITICAL |
| Wrong HTTP status codes | 4 | CRITICAL |
| Swallowed with log-only (no re-throw) | 15+ | HIGH |
Swallowed in loops (continue) |
8+ | HIGH |
| Specific exception thrown as generic (context lost) | 8 | HIGH |
| Exception cause not chained | 5 | HIGH |
| Silent null returns | 2 | MEDIUM |
e.printStackTrace() in production |
2 | MEDIUM |
Broad catch (Exception) losing type info |
3 | MEDIUM |
Instances where exceptions (including NullPointerException) are caught and never bubble up.
File: Digit-Core/core-services/libraries/tracer/src/main/java/org/egov/tracer/http/filters/TracerFilter.java
Line: 206
@SuppressWarnings("unchecked")
private String getCorrelationIdFromBody(HttpServletRequest httpServletRequest) {
String correlationId = null;
try {
final HashMap<String, Object> requestMap = (HashMap<String, Object>)
objectMapper.readValue(httpServletRequest.getInputStream(), HashMap.class);
Object requestInfo = requestMap.containsKey(REQUEST_INFO_FIELD_NAME_IN_JAVA_CLASS_CASE)
? requestMap.get(REQUEST_INFO_FIELD_NAME_IN_JAVA_CLASS_CASE)
: requestMap.get(REQUEST_INFO_IN_CAMEL_CASE);
if (isNull(requestInfo))
return null;
else {
if (requestInfo instanceof Map) {
correlationId = (String) ((Map) requestInfo).get(CORRELATION_ID_FIELD_NAME);
}
}
} catch (IOException ignored){} // ← SWALLOWED: completely silent
return correlationId;
}Impact: IOException (which can wrap NPE during deserialization) is silently eaten. Correlation IDs silently go missing, making distributed tracing unreliable.
File: Digit-Core/core-services/egov-data-uploader/src/main/java/org/egov/dataupload/service/DataUploadService.java
Lines: 567–574
try {
errors = JsonPath.read(response, "$.Errors");
} catch (PathNotFoundException pe) {
logger.error("Unable to get Errors object from Error Response, trying Error object");
try {
Error error = objectMapper.readValue(response, Error.class);
failureMessage.append(error.toString());
failureMessage.append(", ");
} catch (PathNotFoundException | IOException ignored) {} // ← SWALLOWED: silent empty block
}Impact: If the error response can't be parsed at all, the failure reason is lost. Uploads appear to succeed when they actually failed.
File: Digit-Core/core-services/egov-data-uploader/src/main/java/org/egov/dataupload/service/DataUploadService.java
Lines: 238–240
try {
// ... file processing, row iteration, API calls ...
} catch (IOException e) {
logger.error("Unable to open file provided.", e);
uploadJob.setEndTime(new Date().getTime());
uploadJob.setSuccessfulRows(0);
uploadJob.setStatus(StatusEnum.fromValue("failed"));
uploadJob.setReasonForFailure(e.getMessage());
updateJobsWithPersister(auditDetails, uploadJob, false);
throw new CustomException(/* ... */);
} catch (Exception e) {
logger.error("Unknown error ", e);
// ← SWALLOWED: NPE here means upload silently stops, job status never updated
}Impact: Any NPE during Excel processing is caught, logged, and then the method returns normally. The upload job status is never set to "failed" — it stays in a zombie state.
File: digit-2.9lts-core-storm/utilities/default-data-handler/src/main/java/org/egov/handler/util/WorkflowUtil.java
Lines: 34–37
try {
restTemplate.postForObject(uri.toString(), businessServiceRequest, Map.class);
} catch (Exception e) {
log.error("Error creating workflow configuration: {}", e.getMessage());
// ← SWALLOWED: workflow config creation silently fails
}Impact: Workflow configuration fails silently. Downstream processes that depend on workflow being configured will fail with confusing errors.
File: digit-2.9lts-core-storm/utilities/default-data-handler/src/main/java/org/egov/handler/service/DataHandlerService.java
Lines: 132–134
for (JsonNode userNode : userArray) {
try {
// ... user creation logic with property accesses on userNode ...
} catch (Exception e) {
log.error("Failed to create user from payload: {} | Error: {}", userNode, e.getMessage());
// ← SWALLOWED: NPE from null userNode fields, loop continues
}
}Impact: If a user record has a null field that causes NPE, that user is silently skipped. No summary of which users failed.
File: digit-2.9lts-core-storm/utilities/default-data-handler/src/main/java/org/egov/handler/service/DataHandlerService.java
Lines: 254–258
try {
restTemplate.postForObject(mdmsSchemaCreateUri, request, Object.class);
} catch (Exception innerEx) {
log.error("Failed to create schema: {} for tenant: {}. Skipping...",
schemaNode.get("code"), tenantId, innerEx);
// ← SWALLOWED: schema creation fails silently, "Skipping..."
}Impact: MDMS schemas silently fail to create. Data lookups depending on these schemas will fail later with no trace back to this root cause.
File: digit-2.9lts-core-storm/utilities/default-data-handler/src/main/java/org/egov/handler/service/DataHandlerService.java
Lines: 359–362
} catch (Exception ex) {
log.error("Failed to create individual boundary relationship entry for tenant: {}. Skipping...",
targetTenantId, ex);
// ← SWALLOWED: boundary data incomplete, loop continues
}Impact: Boundary relationships silently go missing. Location-based queries will return incomplete results.
File: digit-2.9lts-core-storm/utilities/default-data-handler/src/main/java/org/egov/handler/repository/ServiceRequestRepository.java
Lines: 38–43
Object response = null;
try {
response = restTemplate.postForObject(uri.toString(), request, Map.class);
} catch (HttpClientErrorException e) {
log.error(EXTERNAL_SERVICE_EXCEPTION, e);
throw new ServiceCallException(e.getResponseBodyAsString());
} catch (Exception e) {
log.error(SEARCHER_SERVICE_EXCEPTION, e);
// ← SWALLOWED: generic Exception logged but NOT re-thrown
}
return response; // ← returns null on failureImpact: Callers receive null instead of an exception, leading to NPE at the call site. The original cause is lost in logs.
File: Digit-Core/core-services/egov-indexer/src/main/java/org/egov/infra/indexer/service/DataTransformationService.java
Lines: 181–184
try {
documentContext.put(expression, expressionArray[expressionArray.length - 1],
JsonPath.read(kafkaJson, fieldMapping.getInjsonpath()));
} catch (Exception e) {
log.error("Error while building custom JSON for index: " + e.getMessage());
continue; // ← SWALLOWED: field silently missing from index
}Impact: Index documents get written with missing fields. Search queries against these fields return no results — silently.
File: Digit-Core/core-services/egov-indexer/src/main/java/org/egov/infra/indexer/service/DataTransformationService.java
Lines: 216–220
try {
// ... external API call and response parsing ...
response = mapper.readValue(jsonContent, Map.class);
} catch (Exception e) {
log.error("Exception while making external call: ", e);
log.error("URI: " + uri);
continue; // ← SWALLOWED: enrichment data missing from index
}Impact: External enrichment data silently missing from indexed documents.
File: Digit-Core/core-services/egov-indexer/src/main/java/org/egov/infra/indexer/service/DataTransformationService.java
Lines: 234–239
try {
Object value = JsonPath.read(mapper.writeValueAsString(response), inputJsonPath);
documentContext.put(expression, expressionArray[expressionArray.length - 1], value);
} catch (Exception e) {
log.error("Value: " + fieldMapping.getInjsonpath() + " is not found!");
log.debug("URI: " + uri);
documentContext.put(expression, expressionArray[expressionArray.length - 1], null);
continue; // ← SWALLOWED: null written to index instead of real value
}Impact: Null values silently replace real data in the index. Particularly dangerous because it actively writes null.
File: Digit-Core/core-services/egov-indexer/src/main/java/org/egov/infra/indexer/service/DataTransformationService.java
Lines: 273–277
try {
response = indexerUtils.fetchMdmsData(uri, ...);
} catch (Exception e) {
log.error("Exception while trying to hit: " + uri);
log.info("MDMS Request failure: " + e);
continue; // ← SWALLOWED: MDMS lookup failure hidden
}Impact: MDMS master data silently missing from indexed records. Reports and dashboards show incomplete data.
File: Digit-Core/core-services/egov-indexer/src/main/java/org/egov/infra/indexer/service/DataTransformationService.java
Lines: 290–295
try {
Object value = JsonPath.read(mapper.writeValueAsString(response), fieldMapping.getInjsonpath());
documentContext.put(expression, expressionArray[expressionArray.length - 1], value);
} catch (Exception e) {
log.error("Value: " + fieldMapping.getInjsonpath() + " is not found!");
log.debug("MDMS Request: " + request);
documentContext.put(expression, expressionArray[expressionArray.length - 1], null);
continue; // ← SWALLOWED: null written to index, loop continues
}Impact: Same as A11 — null values silently replace real MDMS-sourced data.
File: Digit-Core/core-services/egov-user/src/main/java/org/egov/user/domain/service/UserService.java
Lines: 436–440
if ((oldEmail != null && !oldEmail.isEmpty()) && newEmail != null && !(newEmail.equalsIgnoreCase(oldEmail))) {
try {
notificationUtil.sendEmail(requestInfo, existingUser, updatedUser);
} catch (Exception ignore) {
log.error("Not able to send email");
// ← SWALLOWED: user never notified their email changed
}
}Impact: Security-sensitive notification (email change) silently fails. User is not informed their contact email was changed.
File: digit-2.9lts-core-storm/digit-oss-sparse/core-services/egov-user/src/test/java/org/egov/user/persistence/repository/FileStoreRepositoryTest.java
Lines: 41–44, 58–61, 74–77
try {
List<String> list = new ArrayList<String>();
list.add("key");
fileStoreUrl = fileStoreRepository.getUrlByFileStoreId("default", list);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace(); // ← SWALLOWED: test silently passes even on failure
}Impact: Tests that should fail on exception silently pass. False confidence in test coverage.
File: Digit-Core/core-services/egov-user/src/main/java/org/egov/user/web/contract/UserRequest.java
Lines: 212–218
BloodGroup bloodGroup = null;
try {
if (this.bloodGroup != null)
bloodGroup = BloodGroup.valueOf(this.bloodGroup.toUpperCase());
} catch (Exception e) {
bloodGroup = BloodGroup.fromValue(this.bloodGroup);
// ← NPE RISK: if this.bloodGroup becomes null between check and catch,
// or if fromValue() doesn't handle the value gracefully
}Impact: Enum parsing failure could cascade into NPE in the catch block itself.
File: digit-2.9lts-core-storm/utilities/default-data-handler/src/main/java/org/egov/handler/util/LocalizationUtil.java
Lines: 81–85
try {
restTemplate.postForObject(uri, createMessagesRequest, ResponseInfo.class);
log.info("Localization batch [{}-{}] upserted successfully for tenant: {}", i + 1, end, tenantId);
} catch (Exception e) {
log.error("Failed to upsert localization batch [{}-{}] for tenant: {}. Skipping... Reason: {}",
i + 1, end, tenantId, e.getMessage());
// ← SWALLOWED: localization entries silently missing
}Impact: Localization messages silently fail to load. UI shows raw message codes instead of translated text.
Instances where exceptions are caught but re-thrown as the wrong type, with misleading codes, or with lost context.
File: Digit-Core/core-services/gateway/src/main/java/com/example/gateway/utils/ExceptionUtils.java
Lines: 67–70
else if (exceptionName.equalsIgnoreCase("CustomException")) {
CustomException ce = (CustomException) e;
return _setExceptionBody(exchange, HttpStatus.valueOf(401),
getErrorInfoObject(exceptionName, exceptionMessage, exceptionMessage));
}What's wrong: Every CustomException — validation errors, not-found, conflicts, server errors — all return 401 Unauthorized. A tenant not found looks like an auth failure.
Should be:
HttpStatus status = mapErrorCodeToHttpStatus(ce.getCode());
return _setExceptionBody(exchange, status, ...);File: Digit-Core/core-services/gateway/src/main/java/com/example/gateway/filters/pre/helpers/RequestEnrichmentFilterHelper.java
Lines: 96–100
try {
httpHeaders.add(USER_INFO_HEADER_NAME, objectMapper.writeValueAsString(user));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}What's wrong: JSON serialization failure (likely bad user data — a 400 issue) becomes RuntimeException → 500 Internal Server Error. Clients can't distinguish "your token has bad data" from "server crashed."
Should be:
throw new CustomException("USER_SERIALIZATION_ERROR",
"Failed to serialize user info: " + e.getOriginalMessage(), e);File: Digit-Core/core-services/egov-user/src/main/java/org/egov/user/persistence/repository/FileStoreRepository.java
Lines: 44–48
try {
fileStoreUrls = restTemplate.getForObject(Url, Map.class);
} catch (HttpClientErrorException e) {
throw new RuntimeException(e.getResponseBodyAsString());
}What's wrong: The HttpClientErrorException carries the original status code (404, 403, etc.) but it's thrown as RuntimeException with just the body string. A 404 from filestore becomes a 500 to the client.
Should be:
catch (HttpClientErrorException e) {
throw new CustomException("FILESTORE_" + e.getStatusCode().value(),
"FileStore error: " + e.getResponseBodyAsString(), e);
}File: Digit-Core/core-services/egov-user/src/main/java/org/egov/user/persistence/repository/OtpRepository.java
Lines: 60–62
catch (HttpClientErrorException e) {
log.error("Otp validation failed", e);
throw new ServiceCallException(e.getResponseBodyAsString());
}What's wrong: HttpClientErrorException includes the HTTP status (401 for invalid OTP, 429 for rate limit, 503 for service down). Only the body is passed to ServiceCallException — caller can't distinguish "wrong OTP" from "OTP service down."
Should be:
throw new ServiceCallException(e.getStatusCode() + ": " + e.getResponseBodyAsString(), e);File: Digit-Core/core-services/egov-pg-service/src/main/java/org/egov/pg/service/TransactionService.java
Lines: 115–120
try {
return transactionRepository.fetchTransactions(transactionCriteria);
} catch (DataAccessException e) {
log.error("Unable to fetch data from the database for criteria: " + transactionCriteria, e);
throw new CustomException("FETCH_TXNS_FAILED", "Unable to fetch transactions from store");
}What's wrong: DataAccessException has specific subtypes — QueryTimeoutException, DataIntegrityViolationException, CannotAcquireLockException, etc. All are flattened to "Unable to fetch transactions from store." A timeout looks the same as a constraint violation.
Should be:
throw new CustomException("DB_FETCH_FAILED",
"Transaction query failed: " + e.getMostSpecificCause().getMessage(), e);File: Digit-Core/core-services/national-dashboard-ingest/src/main/java/org/egov/nationaldashboardingest/repository/ElasticSearchRepository.java
Lines: 67–73
catch (ResourceAccessException e) {
log.error("ES is down");
throw new CustomException("EG_ES_ERR", "Elastic search is down");
} catch (Exception e) {
log.error("Exception while indexing data onto ES.");
throw new CustomException("EG_ES_IDX_ERR", e.getMessage());
}What's wrong: "ES is down" (connectivity) vs "indexing error" (data format) need different error codes and different handling by the caller. The first might warrant a retry; the second needs data fixes. Neither chains the cause.
Should be:
catch (ResourceAccessException e) {
throw new CustomException("ES_CONNECTION_FAILED", "ElasticSearch unreachable", e);
} catch (JsonProcessingException e) {
throw new CustomException("ES_DATA_FORMAT_ERR", "Invalid data for indexing: " + e.getMessage(), e);
} catch (Exception e) {
throw new CustomException("ES_INDEXING_ERR", "Unexpected indexing error", e);
}File: Digit-Core/core-services/zuul/src/main/java/org/egov/wrapper/CustomRequestWrapper.java
Lines: 22–26
try {
payload = IOUtils.toString(request.getInputStream());
} catch (IOException e) {
throw new CustomException("INPUT_TO_STRING_CONVERSION_ERROR", e.getMessage());
}What's wrong: The IOException cause is not chained. The stack trace of the original error is lost. When this shows up in logs, you can't tell where the IOException originated.
Should be:
throw new CustomException("INPUT_TO_STRING_CONVERSION_ERROR",
"Failed to read request payload", e); // chain causeFile: Digit-Core/core-services/egov-indexer/src/main/java/org/egov/infra/indexer/util/IndexerUtils.java
Lines: 325–330
catch (JsonProcessingException e) {
log.error("JsonProcessingException: ", e);
throw new ServiceCallException(e.getMessage());
}What's wrong: JsonProcessingException (a parsing/serialization error) is thrown as ServiceCallException (implies a remote service call failed). The caller will retry the service call when the actual issue is malformed JSON.
Should be:
throw new CustomException("JSON_PARSE_ERROR",
"Failed to parse response: " + e.getOriginalMessage(), e);File: Digit-Core/core-services/boundary-service/src/main/java/digit/service/validator/BoundaryEntityValidator.java
Lines: 73–87
try {
if (boundary.getGeometry().get(BoundaryConstants.TYPE).asText().equals(BoundaryConstants.POINT)) {
GeoUtil.validatePointGeometry(objectMapper.treeToValue(boundary.getGeometry(), PointGeometry.class));
}
// ...
} catch (JsonProcessingException e) {
throw new CustomException(ErrorCodes.INVALID_GEOJSON_CODE, ErrorCodes.INVALID_GEOJSON_MSG);
}What's wrong: The JsonProcessingException tells you exactly which field was wrong and why. The generic "INVALID_GEOJSON" message gives the API consumer no actionable information.
Should be:
throw new CustomException(ErrorCodes.INVALID_GEOJSON_CODE,
ErrorCodes.INVALID_GEOJSON_MSG + ": " + e.getOriginalMessage(), e);File: Digit-Core/core-services/libraries/enc-client/src/main/java/org/egov/encryption/util/JacksonUtils.java
Lines: 113–115
catch (ClassCastException e) {
log.error("Cannot find value for path : " + filterPath);
}What's wrong: ClassCastException is caught, a misleading message ("Cannot find value" — it's actually a type mismatch, not a missing value) is logged, and the method silently returns null. Encryption/decryption silently produces incomplete data.
Should be:
catch (ClassCastException e) {
throw new CustomException("JSON_TYPE_MISMATCH",
"Type mismatch at path: " + filterPath + " - expected compatible type", e);
}File: Digit-Core/core-services/audit-service/src/main/java/org/egov/auditservice/persisterauditclient/PersisterAuditClientService.java
Lines: 84–97
catch (Exception e) {
e.printStackTrace(); // ← anti-pattern in production
log.error("AUDIT_LOG_ERROR", "Failed to create audit log for: " + rowDataList);
AuditError auditError = AuditError.builder()
// ...builds error object...
.build();
kafkaTemplate.send(auditErrorTopic, auditError);
}What's wrong: (1) e.printStackTrace() writes to stderr outside the logging framework — these messages are often lost in containerized environments. (2) The AuditError sent to Kafka doesn't include the exception stack trace, so the error topic consumer can't diagnose the issue.
Should be:
catch (Exception e) {
log.error("Failed to create audit log for: {}", rowDataList, e);
AuditError auditError = AuditError.builder()
.exceptionMessage(e.getMessage())
.exceptionStackTrace(ExceptionUtils.getStackTrace(e))
.build();
kafkaTemplate.send(auditErrorTopic, auditError);
}File: Digit-Core/core-services/egov-url-shortening/src/main/java/org/egov/url/shortening/service/URLConverterService.java
Lines: 98–103
try {
urlRepository.saveUrl("url:" + id, shortenRequest);
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}What's wrong: URL save fails silently. The API returns a shortened URL that doesn't actually exist in the database. When someone clicks the short URL, they get a 404 — but the original API call returned success.
Should be:
catch (JsonProcessingException e) {
throw new CustomException("URL_SAVE_FAILED",
"Failed to persist shortened URL: " + e.getMessage(), e);
}File: Digit-Core/core-services/report/src/main/java/org/egov/report/repository/ReportRepository.java
Lines: 104–130
catch (DataAccessResourceFailureException ex) {
PSQLException cause = (PSQLException) ex.getCause();
if (cause != null && cause.getSQLState().equals("57014")) {
throw new CustomException("QUERY_EXECUTION_TIMEOUT", "Query failed...");
} else {
throw ex; // ← raw DataAccessResourceFailureException escapes
}
} catch (Exception e) {
throw new CustomException("QUERY_EXEC_ERROR", "Error while executing query: " + e.getMessage());
}What's wrong: The non-timeout branch throws raw DataAccessResourceFailureException while all other branches throw CustomException. This means callers get inconsistent error formats depending on the specific database failure.
Should be:
} else {
throw new CustomException("DB_ACCESS_FAILURE",
"Database query failed: " + ex.getMostSpecificCause().getMessage(), ex);
}File: Digit-Core/core-services/mdms-v2/src/main/java/org/egov/infra/mdms/service/validator/MdmsDataValidator.java
Lines: 89–91
catch (Exception e) {
throw new CustomException("MASTER_DATA_VALIDATION_ERR",
"An unknown error occurred while validating provided master data against the schema - " + e.getMessage());
}What's wrong: The word "unknown" in a user-facing error message is a red flag. ValidationException (bad data), JsonProcessingException (bad JSON format), and NullPointerException (missing schema) all get the same "unknown error" message.
Should be:
catch (ValidationException e) {
throw new CustomException("SCHEMA_VALIDATION_FAILED", e.getErrorMessage());
} catch (JsonProcessingException e) {
throw new CustomException("INVALID_JSON_FORMAT", "Data is not valid JSON: " + e.getMessage(), e);
} catch (Exception e) {
throw new CustomException("MASTER_DATA_VALIDATION_ERR",
"Validation failed unexpectedly: " + e.getMessage(), e);
}Across the codebase, CustomException is almost always thrown as:
throw new CustomException("CODE", "message");Instead of:
throw new CustomException("CODE", "message", originalException);This means stack traces are lost everywhere. When you see CustomException in logs, you can never trace back to the original failure.
The gateway maps ALL CustomException to 401. There's no mechanism to return 400 (bad request), 404 (not found), or 409 (conflict) from service-layer exceptions.
Some services throw ServiceCallException for parsing errors, others throw CustomException for service call failures. There's no consistent taxonomy.
| Priority | Fix | Impact |
|---|---|---|
| P0 | Fix gateway ExceptionUtils — stop returning 401 for everything (B1) |
All API consumers get correct status codes |
| P0 | Chain exception causes in all CustomException throws (C1) |
Stack traces available for debugging |
| P0 | Fix empty catch blocks (A1, A2) | Failures become visible |
| P0 | Fix zombie job state in DataUploadService (A3) | Upload jobs report correct status |
| P1 | Fix silent null returns (A8) | Stop NPE cascades at call sites |
| P1 | Fix index data corruption — DataTransformationService (A9–A13) | Indexed documents have complete data |
| P1 | Fix security notification swallowing (A14) | Users notified of email changes |
| P1 | Replace RuntimeException throws with CustomException (B2, B3) |
Consistent error format |
| P1 | Replace printStackTrace() with proper logging (B11, B12) |
Errors visible in log aggregation |
| P2 | Fix loop swallowing — collect errors and report summary (A5–A7, A17) | Batch failures reported, not hidden |
| P2 | Fix workflow config swallowing (A4) | Tenant setup fails visibly |
| P2 | Split broad catch (Exception) into specific catches (B6, B14) |
Better error messages for API consumers |
| P2 | Establish error code taxonomy (C3) | Consistent error handling across services |
| P3 | Fix test code — let exceptions propagate (A15) | Accurate test coverage |
// BEFORE:
} catch (Exception e) {
log.error("Something failed", e);
}
// AFTER:
} catch (Exception e) {
log.error("Something failed", e);
throw new CustomException("DESCRIPTIVE_CODE", "Something failed: " + e.getMessage(), e);
}List<String> errors = new ArrayList<>();
for (...) {
try { ... }
catch (Exception e) {
errors.add("Item X failed: " + e.getMessage());
log.error("...", e);
}
}
if (!errors.isEmpty()) {
throw new CustomException("BATCH_PARTIAL_FAILURE",
errors.size() + " of " + total + " items failed: " + String.join("; ", errors));
}// BEFORE:
throw new CustomException("CODE", e.getMessage());
// AFTER:
throw new CustomException("CODE", "Meaningful message", e);