Skip to content

Deepen domain error semantics so not-found, bad-request, and forbidden cases return correct HTTP status codes#42

Merged
Pan14ek merged 3 commits into
mainfrom
bug/domain-error-semantics
Jun 1, 2026
Merged

Deepen domain error semantics so not-found, bad-request, and forbidden cases return correct HTTP status codes#42
Pan14ek merged 3 commits into
mainfrom
bug/domain-error-semantics

Conversation

@Pan14ek
Copy link
Copy Markdown
Owner

@Pan14ek Pan14ek commented Jun 1, 2026

Purpose

Raw NoSuchElementException, IllegalArgumentException, and SecurityException leaked
from event, task, and lobby modules into the generic catch-all handler, returning HTTP 500
for every not-found, bad-request, and forbidden case. This PR deepens the lookup and domain
error seam so all three scenarios use application exception types routed through
BaseAppExceptionGlobalExceptionHandlerProblemDetail with correct HTTP status codes.

Type

  • 🧪 Experiment (fitness function research)
  • ✨ Feature (new business logic)
  • 🐛 Bug fix
  • ♻️ Refactor / neutral change
  • 📝 Documentation only

Changes

Two new exception classes extend BaseAppException: BadRequestException (400,
common.bad_request) and ForbiddenException (403, common.forbidden), following the
same pattern as the existing NotFoundException and ConflictException. GlobalExceptionHandler
gains an explicit FORBIDDEN title case for consistency.

LobbyAccessPolicy.ensureMember and ensureOwner now throw ForbiddenException instead
of SecurityException. Both methods also gain a Objects.requireNonNull(lobby) guard.

All mustUser, mustLobby, mustEvent, and mustTask helpers now pass NotFoundException
suppliers to EntityFinder.findOrThrow instead of throwing NoSuchElementException directly.
Date-range checks in EventServiceImpl and the owner-removal guard in LobbyServiceImpl
throw BadRequestException instead of IllegalArgumentException. TaskServiceImpl.list
wraps TaskStatus.valueOf in a try/catch and throws BadRequestException on an invalid
status string — previously a silent HTTP 500.

EventEntity.@PrePersist had a duplicate startAt < endAt check that also threw
IllegalArgumentException. The service layer validates the same constraint before repo.save(),
so the entity-level check was unreachable in normal flow and a latent HTTP 500 risk from
any future bypass. It is removed.

EventService and TaskService interface @throws Javadoc updated to reflect the actual
thrown types — the old tags documented NoSuchElementException, SecurityException, and
IllegalArgumentException.

All affected tests updated to assert NotFoundException, BadRequestException, or
ForbiddenException. Test method names renamed accordingly (e.g. throwsNoSuchElement_
throwsNotFound_). One new test added: list_throwsBadRequest_whenStatusIsInvalid.

Files changed

File Change
common/exception/BadRequestException.java Created
common/exception/ForbiddenException.java Created
config/GlobalExceptionHandler.java Added FORBIDDEN title case
lobby/service/LobbyAccessPolicy.java SecurityExceptionForbiddenException; null guard on lobby
event/service/EventService.java @throws updated to application exception types
event/service/EventServiceImpl.java NoSuchElementExceptionNotFoundException; IllegalArgumentExceptionBadRequestException
event/domain/EventEntity.java Removed IllegalArgumentException from @PrePersist
task/service/TaskService.java @throws updated to application exception types
task/service/TaskServiceImpl.java NoSuchElementExceptionNotFoundException; TaskStatus.valueOf wrapped with BadRequestException
lobby/service/LobbyServiceImpl.java NoSuchElementExceptionNotFoundException; IllegalArgumentExceptionBadRequestException
lobby/service/LobbyAccessPolicyTest.java SecurityExceptionForbiddenException assertions
event/service/EventServiceImplTest.java All raw exception assertions replaced; method names updated
event/service/EventServiceImplConflictTest.java Same
task/service/TaskServiceImplTest.java Same; added list_throwsBadRequest_whenStatusIsInvalid
lobby/service/LobbyServiceImplTest.java Same

Expected result

Metric Baseline (main) Branch Direction
checkstyle_violations 0 0 neutral
spotbugs_total 0 0 neutral
line_coverage 54.3% 54.9% increase
critical_violations neutral
code_smells neutral
duplicated_lines_density neutral
F score neutral or slight increase
SonarQube QG neutral

Checklist

  • ./gradlew check passes locally
  • ./gradlew jacocoTestReport passes locally
  • No unintended changes to main business logic
  • Branch name matches experiment/feature naming convention

Pan14ek and others added 3 commits June 1, 2026 00:57
…lobalExceptionHandler

Introduces two new application exception types extending BaseAppException so
400 and 403 responses flow through the ProblemDetail seam instead of leaking
raw Java exceptions. Adds an explicit FORBIDDEN title case to the handler
switch for consistency with the other mapped statuses.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion exceptions

- LobbyAccessPolicy: SecurityException -> ForbiddenException in ensureMember/ensureOwner;
  add Objects.requireNonNull(lobby) guard in both methods
- EventServiceImpl: NoSuchElementException -> NotFoundException via EntityFinder.findOrThrow
  in all must* helpers; IllegalArgumentException -> BadRequestException for date-range checks
- TaskServiceImpl: same NotFoundException pattern; wrap TaskStatus.valueOf with
  BadRequestException to prevent HTTP 500 on invalid status strings
- LobbyServiceImpl: NoSuchElementException -> NotFoundException; IllegalArgumentException
  -> BadRequestException for owner-removal guard
- EventEntity: remove IllegalArgumentException from @PrePersist; service layer validates
  startAt < endAt before save, making the entity-level check both duplicate and a 500 risk
- EventService/TaskService interfaces: update @throws Javadoc to reflect actual exception types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all NoSuchElementException -> NotFoundException, IllegalArgumentException ->
BadRequestException, SecurityException -> ForbiddenException assertions across all
affected service and policy test files. Rename test methods accordingly
(e.g. throwsNoSuchElement_ -> throwsNotFound_) for clarity. Add
list_throwsBadRequest_whenStatusIsInvalid to cover the TaskStatus.valueOf guard.
Remove all imports of NoSuchElementException from test files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 1, 2026

@Pan14ek Pan14ek merged commit dad3925 into main Jun 1, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant