11Critical Logic Bugs Found During Grails 7 Migration Review
22===========================================================
33
4- FIXED ISSUES
5- -------------
4+ This file contains only CRITICAL bugs: those that cause crashes, data corruption,
5+ prevent app startup, or completely break major features. Non-critical bugs and
6+ Groovy 4 compatibility issues are documented in separate files.
7+
8+ CRITICAL BUGS (CAUSES CRASHES, DATA LOSS, OR WON'T START)
9+ ---------------------------------------------------------
610
7111. MISSING RETURN AFTER ERROR RENDER (all controllers) [FIXED]
812 ~100 methods across all controllers sent an error response but kept
@@ -11,12 +15,6 @@ FIXED ISSUES
1115 even after a permission error was already sent back. Server crashes from
1216 trying to send two responses.
1317
14- 2. Bare `render jsonObject` without `as JSON` [FIXED]
15- ~56 render calls across 4 controllers sent JSON objects without setting
16- the content type.
17- CONSEQUENCE: Clients receive JSON data labeled as HTML. Browser-side
18- JSON parsing fails, breaking the annotation editor UI.
19-
20183. PreferenceService NPEs on lines 161, 243, 298-299, 373-374 [FIXED]
2119 Accessed properties on possibly-null preference objects.
2220 CONSEQUENCE: Server crash (500 error) when a user has no saved
@@ -43,23 +41,12 @@ FIXED ISSUES
4341 Set/Map operations on FeatureProperty objects silently malfunction
4442 (contains() always returns false, remove() never matches, etc.).
4543
46- 8. AnnotatorController variant method duplication [FIXED]
47- 7 copy-pasted methods collapsed into a shared helper.
48- CONSEQUENCE: No runtime bug — just maintenance risk where a fix to
49- one copy wouldn't be applied to the other six.
50-
51449. PreferenceService empty list .first() calls (lines 196, 698, 709) [FIXED]
5245 Calls .first() on lists that may be empty.
5346 CONSEQUENCE: Server crash during login or organism-switching when
5447 the database has no sequences or organisms. Common during initial
5548 setup — the app becomes unusable until data is loaded.
5649
57- 10. Quartz scheduling replaced with Spring @Scheduled [FIXED]
58- CleanupPreferencesJob used the Quartz plugin, which has no Grails 7
59- compatible version.
60- CONSEQUENCE: Without this fix, preference cleanup never runs, and
61- stale preferences accumulate indefinitely in the database.
62-
635011. FeatureService.groovy NPE risks [FIXED]
6451 - Line 803: Assumes a transcript always has a feature location.
6552 - Lines 1528-1529: Assumes CV terms always exist in the database.
@@ -78,29 +65,6 @@ FIXED ISSUES
7865 500 instead of a helpful error message.
7966 FIX: Added null checks with error logging and skip/continue.
8067
81- 13. PermissionService.groovy dead code (line 420-421) [FIXED]
82- The fallback branch passes null as a query parameter, so the query
83- always returns nothing.
84- CONSEQUENCE: No user-visible bug — the fallback on line 429 handles
85- it. But a wasted database query runs on every permission check where
86- no sequence name is provided. Minor performance cost.
87- FIX: Removed the dead query; branch now directly sets sequence = null,
88- falling through to the existing fallback on line 429.
89-
90- 14. AnnotationEditorController.getInformation permission mismatch (line 301) [FIXED]
91- Checks WRITE permission without the request context.
92- CONSEQUENCE: May check permissions against the wrong organism.
93- Could show or hide feature information incorrectly.
94- FIX: Changed checkPermissions(PermissionEnum.WRITE) to
95- checkPermissions(inputObject, PermissionEnum.WRITE).
96-
97- 15. GroupController.updateGroup sends no response on success (line 246-281) [FIXED]
98- Only the error path sends a response. Success sends nothing.
99- CONSEQUENCE: Client receives an empty response after a successful
100- group update. If the client expects JSON confirmation, it shows an
101- error despite the operation succeeding.
102- FIX: Added `render new JSONObject() as JSON` after successful save.
103-
1046817. Groovy 4 log.error() type mismatch (multiple files) [FIXED]
10569 `log.error(e)` / `log.error(map)` / `log.error(e.printStackTrace())`
10670 pass non-String arguments to SLF4J Logger.error().
@@ -110,11 +74,6 @@ FIXED ISSUES
11074 GroupController, VcfController.
11175 FIX: Changed to `log.error(e.message, e)`.
11276
113- 18. Groovy 4 e.printStackTrace() calls (13 instances, 11 files) [FIXED]
114- Prints to stderr instead of the logging framework.
115- CONSEQUENCE: Error output lost in production (no log file capture).
116- FIX: Replaced all 13 instances with `log.error(e.message, e)`.
117-
1187719. Groovy 4 Date.minus() removed (FeatureEventController line 216) [FIXED]
11978 `today.minus(20*365)` used a method removed in Groovy 4.
12079 CONSEQUENCE: Feature event history queries crash on startup.
@@ -147,7 +106,7 @@ FIXED ISSUES
147106 simplified 4 repeated if-blocks into a loop.
148107
14910823. SequenceController.setCurrentSequence null binding + session NPE [FIXED]
150- Two issues:
109+ Three issues:
151110 a) Method signature `setCurrentSequence(Sequence sequenceInstance)`
152111 relies on Grails 7 domain class data binding, which fails when no
153112 ID is in the URL path. API callers sending `sequenceName` in JSON
@@ -193,28 +152,6 @@ FIXED ISSUES
193152 cannot start at all with a PostgreSQL database.
194153 FIX: Added `failOnError: true, flush: true` and null checks.
195154
196- 28. PermissionService.getOrganismsWithMinimumPermission/getOrganismsWithPermission
197- wrong user (lines 101, 128) [FIXED]
198- Both methods accept a `User user` parameter but call
199- `getOrganismPermissionsForUser(organism, currentUser)` instead of
200- using the passed-in `user`. `currentUser` resolves to `getCurrentUser()`
201- which returns whoever is currently logged in.
202- CONSEQUENCE: When checking permissions for a different user (e.g. admin
203- viewing another user's organism list), the check runs against the admin's
204- own permissions instead. Could show incorrect organism lists in the
205- admin user management UI.
206- FIX: Changed `currentUser` to `user` on both lines.
207-
208- 29. PreferenceService.evaluateSaves dead branch (lines 418-422) [FIXED]
209- Both branches of the `if (onlySaveToken && ...)` / `else` call
210- `evaluateSave()` with identical arguments. The `onlySaveToken` filter
211- has no effect — all tokens are always saved regardless.
212- CONSEQUENCE: When `onlySaveToken` is set (intended to save only one
213- specific client token's preferences), all tokens are saved instead.
214- Minor — causes unnecessary database writes but no data corruption.
215- FIX: Changed to `if (!onlySaveToken || onlySaveToken == ...)` with
216- a single `evaluateSave()` call, so non-matching tokens are skipped.
217-
21815530. FeatureService.convertFeatureToJSONLite wrong variable in owner loop
219156 (line 2042) [FIXED]
220157 Loop iterates `for (owner in gsolFeature.owners)` but body uses
@@ -224,13 +161,6 @@ FIXED ISSUES
224161 If gsolFeature.owner is null but gsolFeature.owners is non-empty, NPE.
225162 FIX: Changed to `owner.username`.
226163
227- 31. AnnotationEditorController.getUserPermission dead code (lines 85-88) [FIXED]
228- Built a permissions HashMap with one entry, then immediately overwrote
229- it with `permissionService.getPermissionsForUser(user)`.
230- CONSEQUENCE: No runtime bug — 4 lines of dead code wasting CPU on
231- an unused organism permission lookup every time getUserPermission runs.
232- FIX: Removed the dead lines; kept only the effective call.
233-
23416432. @Transactional closure conversions — second pass [FIXED]
235165 Converted remaining .each{}, .collect{}, .findAll{} closures to for
236166 loops in three more files:
@@ -253,13 +183,6 @@ FIXED ISSUES
253183 added to the wrong allele. Allele updates are silently corrupted.
254184 FIX: Changed to use `oldAlleleBases` and `newAlleleBases` respectively.
255185
256- 34. VariantService.updateAlternateAlleles undefined variables in log (line 101) [FIXED]
257- Log message referenced `${oldAltAlleleFrequency}` and `${oldProvenance}`
258- which are never defined in the method.
259- CONSEQUENCE: Groovy resolves undefined variables as null, so the log
260- message prints "null" instead of useful information. Not a crash.
261- FIX: Simplified log message to use defined variables.
262-
26318635. VariantService.deleteAlleleInfo missing save (line 216) [FIXED]
264187 Method returns `feature` without calling `feature.save()`. Every other
265188 similar method in VariantService calls `save(flush: true, failOnError: true)`.
@@ -274,15 +197,6 @@ FIXED ISSUES
274197 no parent gene (e.g. orphaned transcripts during complex editing).
275198 FIX: Moved `gene.save()` inside the `if (gene)` block.
276199
277- 37. UserController.deleteUser render before delete (lines 558-559) [FIXED]
278- `render jsonObject as JSON` was called before `user.delete()`. The
279- jsonObject was built from `user.properties` which includes internal
280- Hibernate state. After render, the user object is deleted, but the
281- response may reference stale/detached state.
282- CONSEQUENCE: Response may include internal Hibernate properties. If
283- delete fails, the response already told the client it succeeded.
284- FIX: Build a clean JSONObject, delete first, then render.
285-
28620038. GroupController.updateGroup wrong type conversion (line 225) [FIXED]
287201 `dataObject.id.split(',').collect() as Long` casts the entire list
288202 to Long instead of converting each element.
@@ -291,12 +205,6 @@ FIXED ISSUES
291205 management UI breaks for multi-group operations.
292206 FIX: Changed to `.collect { Long.parseLong(it.trim()) }`.
293207
294- 39. GroupController.updateGroup wrong error message (line 265) [FIXED]
295- Error message says "Failed to delete the group" in the updateGroup
296- method. Copy-paste from deleteGroup.
297- CONSEQUENCE: Misleading error message shown to admin users.
298- FIX: Changed to "Failed to find the group".
299-
30020840. AnnotatorController.updateFeature status handling (lines 257-260) [FIXED]
301209 Two bugs in status deletion:
302210 a) `Status oldStatus = data.status` — should be `feature.status` (data.status
@@ -316,58 +224,3 @@ FIXED ISSUES
316224 CONSEQUENCE: FASTA export crashes with NPE for features that have
317225 no retrievable sequence (e.g. features on unloaded sequences).
318226 FIX: Combined null and empty check: `if (seq == null || seq.length() == 0)`.
319-
320- 42. CdsService.setStopCodonReadThrough duplicate relationship (lines 120-135) [FIXED]
321- When `replace=true`, `setChildForType()` replaces the existing
322- relationship. But then lines 125-131 ALWAYS create a new
323- FeatureRelationship regardless, resulting in a duplicate.
324- CONSEQUENCE: Two identical CDS→StopCodonReadThrough relationships
325- exist in the database. Downstream code that expects exactly one
326- (like `hasStopCodonReadThrough()`) may behave incorrectly.
327- FIX: If setChildForType succeeds (returns true), save and return
328- early without creating a new relationship.
329-
330- 43. ReportService wrong domain class in HQL queries (lines 82, 84, 162, 163) [FIXED]
331- Four HQL queries used `TransposableElement.executeQuery(...)` to run
332- queries for `RepeatRegion` and `Exon` counts. GORM dispatches queries
333- through the calling domain class's session context.
334- CONSEQUENCE: RepeatRegion and Exon counts in annotator reports may
335- return incorrect results or fail if TransposableElement's mapping
336- doesn't cover those HQL paths.
337- FIX: Changed to `RepeatRegion.executeQuery(...)` and
338- `Exon.executeQuery(...)` respectively.
339-
340- 44. ReportService transcriptCount overwritten (lines 28-34) [FIXED]
341- Lines 28-31 calculated transcriptCount from a type map, then line 34
342- unconditionally overwrote it with `Transcript.count`. The conditional
343- calculation was dead code.
344- FIX: Removed the dead conditional block.
345-
346- 45. ReportService unreachable admin branch (lines 188-200) [FIXED]
347- Inside `if (!isUserGlobalAdmin(owner))`, a nested
348- `if (isUserGlobalAdmin(owner))` is always false. The entire
349- Organism.listOrderByCommonName branch was dead code.
350- FIX: Removed the dead branch, kept only the non-admin path,
351- and converted the .each closure to a for loop.
352-
353- 46. HomeController CSV column order mismatch (line 92) [FIXED]
354- Header said `total,count,mean,max,min,stddev` but data was written
355- as `total,count,min,max,mean,stddev`.
356- CONSEQUENCE: CSV downloads have swapped min/mean columns. Any
357- pipeline parsing by column name would get wrong values.
358- FIX: Changed header to match data order: `total,count,min,max,mean,stddev`.
359-
360- 47. ProxyController null queryString (line 62) [FIXED]
361- `targetUrl += "?" + request.queryString` concatenates "?null" when
362- there are no query parameters.
363- CONSEQUENCE: Proxy requests without query params produce malformed
364- URLs ending in "?null", which may confuse downstream services.
365- FIX: Added null check on request.queryString before appending.
366-
367- REMAINING ISSUES
368- -----------------
369-
370- 16. IOServiceController.write() cleanup after error render (line 256-258)
371- After the catch block renders an error, code continues to run
372- outputFile.deleteOnExit(). This is intentional cleanup, not a bug —
373- but noted for completeness.
0 commit comments