chore: fix compilation warnings from the Spring Boot 4 upgrade - #3854
chore: fix compilation warnings from the Spring Boot 4 upgrade#3854bdshadow wants to merge 15 commits into
Conversation
Kotlin 2.2 tracks definite assignment through init blocks, so properties assigned there no longer need lateinit.
Jakarta Persistence 3.2 deprecates @TeMPOraL. Every usage here was TemporalType.TIMESTAMP on a java.util.Date, which is the default mapping, so removing the annotation keeps the column mapping unchanged.
Spring Boot 4 brings Jackson 3, where JsonNode.asText()/isTextual are deprecated in favour of asString()/isString.
Hibernate 7 moved the SKIP_LOCKED / NO_WAIT magic values to org.hibernate.Timeouts. The lock hint takes an int, so the *_MILLI constants are the direct equivalents.
Jakarta Persistence 3.2 deprecates multiselect in favour of an explicit compound selection: cb.construct for projected views, cb.tuple for tuple queries and cb.array for Object[] queries. Hibernate's unnamed JpaCteContainer.with is deprecated too, so the CTE now gets an explicit name.
Hypersistence deprecated StringArrayType and ListArrayType because Hibernate has supported ARRAY attributes natively since 6.
Interceptor.onSave/onDelete are deprecated in favour of onPersist/onRemove.
Spring 7 deprecates MappingJackson2XmlHttpMessageConverter (superseded by the Jackson 3 JacksonXmlHttpMessageConverter), CachingConfigurerSupport (the CachingConfigurer interface now has defaults), HttpClientErrorException. UnprocessableEntity (422 was renamed to Unprocessable Content) and, in Spring Batch 6, StepBuilder.chunk(size, transactionManager). The WebMvcConfigurer message-converter callbacks stay on the deprecated List overload: the replacement ServerBuilder API bypasses Spring HATEOAS's HAL converter registration.
AWS DefaultCredentialsProvider.create, Redisson getKeysByPattern, commons-lang3 RandomStringUtils.randomAlphanumeric, Sentry User.name, Spring Data getOne/getById, AssertJ isEqualToIgnoringMillis, java.util.Date.UTC and ktlint ASTNode.children().
java.lang.Long/Boolean::class.java is flagged by Kotlin; ::class.javaObjectType yields the same Class instance for the JPA result-type argument.
The typed source property intentionally shadows ApplicationEvent's source field, which stays reachable through getSource().
Casts over erased JPA result lists, cache entries, reflection and parsed test JSON cannot be checked at runtime.
Nested apply/build lambdas produced two labels of the same name, so this@apply and this@build were ambiguous.
Gradle names report files after the test method, and quotes are not valid in Windows filenames.
Kotlin 2.2 propagates nullability further, so a number of safe calls, !! assertions, elvis fallbacks and casts are now provably dead. Two of these hid real problems: MimeMessageParser pinned JavaMail platform types to non-null, which silently disabled its null checks even though getRecipients and getFrom do return null, and ValidationError cast a vararg Array<out String> to Array<String>.
📝 WalkthroughWalkthroughThis pull request modernizes Spring, Hibernate, Jackson, Kotlin, AWS, Redis, JPA, and Kotlin test code. It also updates query projections, entity mappings, test fixtures, and EE integrations. ChangesBackend modernization
Estimated code review effort: 3 (Moderate) | ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
backend/app/src/main/kotlin/io/tolgee/configuration/RestTemplateConfiguration.kt (1)
32-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove
removeXmlConverterbelow both callers.
webhookRestTemplateat Line 38 callsremoveXmlConverter, but the helper is declared at Line 31. Move the helper below both bean methods so each caller appears before the function it calls.Proposed reorder
- private fun RestTemplate.removeXmlConverter(): RestTemplate { - messageConverters.removeIf { it is JacksonXmlHttpMessageConverter } - return this - } - `@Bean`(name = ["webhookRestTemplate"]) fun webhookRestTemplate(): RestTemplate { return RestTemplate(getClientHttpRequestFactory()).removeXmlConverter() } + + private fun RestTemplate.removeXmlConverter(): RestTemplate { + messageConverters.removeIf { it is JacksonXmlHttpMessageConverter } + return this + }As per path instructions: “Functions should be ordered so that a caller appears before the functions it calls.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/src/main/kotlin/io/tolgee/configuration/RestTemplateConfiguration.kt` at line 32, Move the removeXmlConverter helper below both bean methods that call it, including webhookRestTemplate, while leaving its implementation unchanged. Preserve the caller-before-callee ordering throughout the configuration.Source: Path instructions
backend/data/src/main/kotlin/io/tolgee/batch/state/RedisBatchJobStateStorage.kt (1)
212-213: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove
getCachedJobIdsbelowclearUnusedStates.
clearUnusedStatesat Line 220 callsgetCachedJobIdsat Line 212. This violates the Stepdown Rule. MovegetCachedJobIdsbelowclearUnusedStatesso the caller appears before the callee.As per path instructions, “Functions should be ordered so that a caller appears before the functions it calls.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/data/src/main/kotlin/io/tolgee/batch/state/RedisBatchJobStateStorage.kt` around lines 212 - 213, Reorder the methods in RedisBatchJobStateStorage so clearUnusedStates appears before getCachedJobIds, keeping both implementations unchanged and ensuring the caller precedes the callee.Source: Path instructions
ee/backend/tests/src/test/kotlin/io/tolgee/ee/WebhookAutomationTest.kt (1)
133-170: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReorder helpers to satisfy the Stepdown Rule.
verifyWebhookExecutedcallsgetWebhookRestTemplateInvocationCountandverifyWebhookSignature, which are declared before it. Tests at Lines 174-212 and Lines 214-231 callverifyWebhookExecutedafter its declaration. Move the private helpers below all test methods so every caller appears before the functions it calls.As per path instructions: “Functions should be ordered so that a caller appears before the functions it calls.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ee/backend/tests/src/test/kotlin/io/tolgee/ee/WebhookAutomationTest.kt` around lines 133 - 170, Reorder the private helper methods in WebhookAutomationTest so all test methods calling verifyWebhookExecuted appear before it, and verifyWebhookExecuted appears before the lower-level helpers getWebhookRestTemplateInvocationCount and verifyWebhookSignature. Preserve each helper’s implementation and ensure every caller precedes the functions it invokes.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/data/src/main/kotlin/io/tolgee/batch/BatchJobService.kt`:
- Around line 354-357: Update getAllUnlockedChunksForJobs to avoid relying on a
lock hint with its DTO constructor projection; use an entity-returning or native
query that applies FOR UPDATE SKIP LOCKED, then map the selected rows to
JobUnlockedChunk. Ensure getInitialJobId cannot treat database-locked chunks as
unlocked, and add a concurrency test covering two jobs contending for the same
project.
In `@backend/data/src/main/kotlin/io/tolgee/formats/MessagePatternUtil.kt`:
- Line 426: Update the rendering logic around simpleStyle so the comma is
appended only when style is non-null, preserving `{0,number}` for arguments
without a style while retaining styled output.
In
`@ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaCheckPreviewWebSocketHandler.kt`:
- Line 130: Update the text extraction in the QA preview update handler around
the text JSON field: validate that text is scalar before converting it, and
return a QaCheckPreviewError for object or array values instead of calling
asString() on them. Preserve the existing empty/default behavior and add
coverage for both object and array text updates alongside the existing scalar
tests.
In
`@ee/backend/app/src/main/kotlin/io/tolgee/ee/service/prompt/PromptResultParser.kt`:
- Around line 17-18: Update PromptResultParser.parse() to validate output and
contextDescription are scalar JSON values before calling asString(), mapping
object and array values to the existing provider-response error instead of
allowing JsonNodeException to escape. Preserve the current missing-output
behavior, and add parser tests covering object and array values for both fields.
---
Nitpick comments:
In
`@backend/app/src/main/kotlin/io/tolgee/configuration/RestTemplateConfiguration.kt`:
- Line 32: Move the removeXmlConverter helper below both bean methods that call
it, including webhookRestTemplate, while leaving its implementation unchanged.
Preserve the caller-before-callee ordering throughout the configuration.
In
`@backend/data/src/main/kotlin/io/tolgee/batch/state/RedisBatchJobStateStorage.kt`:
- Around line 212-213: Reorder the methods in RedisBatchJobStateStorage so
clearUnusedStates appears before getCachedJobIds, keeping both implementations
unchanged and ensuring the caller precedes the callee.
In `@ee/backend/tests/src/test/kotlin/io/tolgee/ee/WebhookAutomationTest.kt`:
- Around line 133-170: Reorder the private helper methods in
WebhookAutomationTest so all test methods calling verifyWebhookExecuted appear
before it, and verifyWebhookExecuted appears before the lower-level helpers
getWebhookRestTemplateInvocationCount and verifyWebhookSignature. Preserve each
helper’s implementation and ensure every caller precedes the functions it
invokes.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: eb8dfeed-5cb3-48c2-9bdc-633bfe7c9073
📒 Files selected for processing (147)
backend/api/src/main/kotlin/io/tolgee/configuration/CacheConfiguration.ktbackend/api/src/main/kotlin/io/tolgee/configuration/OctetStreamSupportConfiguration.ktbackend/api/src/main/kotlin/io/tolgee/controllers/PublicController.ktbackend/api/src/main/kotlin/io/tolgee/hateoas/invitation/OrganizationInvitationModelAssembler.ktbackend/api/src/main/kotlin/io/tolgee/hateoas/organization/SimpleOrganizationModelAssembler.ktbackend/api/src/main/kotlin/io/tolgee/hateoas/translations/suggestions/TranslationSuggestionModelAssembler.ktbackend/app/src/main/kotlin/io/tolgee/ExceptionHandlers.ktbackend/app/src/main/kotlin/io/tolgee/component/TolgeeSentryUserProvider.ktbackend/app/src/main/kotlin/io/tolgee/configuration/EventStreamConfig.ktbackend/app/src/main/kotlin/io/tolgee/configuration/RestTemplateConfiguration.ktbackend/app/src/main/kotlin/io/tolgee/configuration/WebMvcConfiguration.ktbackend/app/src/test/kotlin/io/tolgee/api/v2/controllers/NullTypedActivityRevisionStorageTest.ktbackend/app/src/test/kotlin/io/tolgee/api/v2/controllers/administration/ProjectExportImportControllerTest.ktbackend/app/src/test/kotlin/io/tolgee/api/v2/controllers/organizationController/OrganizationFloorAccessTest.ktbackend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translationSuggestionController/TranslationSuggestionControllerMtTest.ktbackend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerFilterTest.ktbackend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerHistoryTest.ktbackend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/V2ImportControllerAddFilesTest.ktbackend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeySoftDeleteNamespaceTest.ktbackend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ProjectsController/ProjectsControllerTest.ktbackend/app/src/test/kotlin/io/tolgee/batch/AbstractBatchJobsGeneralTest.ktbackend/app/src/test/kotlin/io/tolgee/controllers/ExportControllerTest.ktbackend/app/src/test/kotlin/io/tolgee/jobs/migration/allOrganizationOwner/AllOrganizationOwnerJobTest.ktbackend/app/src/test/kotlin/io/tolgee/mcp/tools/McpBatchToolsTest.ktbackend/app/src/test/kotlin/io/tolgee/mcp/tools/McpKeyToolsTest.ktbackend/app/src/test/kotlin/io/tolgee/mcp/tools/McpLanguageToolsTest.ktbackend/app/src/test/kotlin/io/tolgee/mcp/tools/McpProjectToolsTest.ktbackend/app/src/test/kotlin/io/tolgee/mcp/tools/McpTagToolsTest.ktbackend/app/src/test/kotlin/io/tolgee/mcp/tools/McpTranslationToolsTest.ktbackend/app/src/test/kotlin/io/tolgee/repository/ProjectRepositoryTest.ktbackend/app/src/test/kotlin/io/tolgee/repository/dataImport/ImportFileRepositoryTest.ktbackend/app/src/test/kotlin/io/tolgee/repository/dataImport/ImportRepositoryTest.ktbackend/app/src/test/kotlin/io/tolgee/service/ActivityVIewByRevisionsProviderTest.ktbackend/app/src/test/kotlin/io/tolgee/service/KeyTrashPurgeSchedulerTest.ktbackend/app/src/test/kotlin/io/tolgee/websocket/WebsocketTestHelper.ktbackend/data/src/main/kotlin/io/tolgee/MtServicesConfiguration.ktbackend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.ktbackend/data/src/main/kotlin/io/tolgee/batch/BatchJobActionService.ktbackend/data/src/main/kotlin/io/tolgee/batch/BatchJobCancellationManager.ktbackend/data/src/main/kotlin/io/tolgee/batch/BatchJobService.ktbackend/data/src/main/kotlin/io/tolgee/batch/ChunkProcessingUtil.ktbackend/data/src/main/kotlin/io/tolgee/batch/MtProviderCatching.ktbackend/data/src/main/kotlin/io/tolgee/batch/cleaning/ScheduledJobCleaner.ktbackend/data/src/main/kotlin/io/tolgee/batch/processors/TagKeysChunkProcessor.ktbackend/data/src/main/kotlin/io/tolgee/batch/processors/UntagKeysChunkProcessor.ktbackend/data/src/main/kotlin/io/tolgee/batch/state/RedisBatchJobStateStorage.ktbackend/data/src/main/kotlin/io/tolgee/component/fileStorage/S3ClientProvider.ktbackend/data/src/main/kotlin/io/tolgee/component/machineTranslation/providers/AzureCognitiveApiService.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/InvitationBuilder.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/BatchJobsTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/BigMetaTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/CommunityContributionE2eData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ConcurrentBatchJobsTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ContributorsTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/GlossaryGuestAccessTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/KeyTrashTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/KeysTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/OrganizationStatsTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ProjectExportImportTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ProjectImportBranchedSourceTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ProjectImportTargetTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ProjectLeavingTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/PublicProjectsControllerTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/QaE2eTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ResolvableImportTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ScopedSearchTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/SlackTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/SoftDeleteBranchingTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/SoftDeleteKeysTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/SuggestionsTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TaskTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TmSuggestionsE2eTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TranslationMemoryTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TranslationSourceChangeStateTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TranslationsSnapshotTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/dataImport/SingleStepImportBranchTestData.ktbackend/data/src/main/kotlin/io/tolgee/dtos/queryResults/organization/OrganizationView.ktbackend/data/src/main/kotlin/io/tolgee/dtos/request/validators/ValidationError.ktbackend/data/src/main/kotlin/io/tolgee/events/OnEntityCollectionPreUpdate.ktbackend/data/src/main/kotlin/io/tolgee/events/OnEntityPreDelete.ktbackend/data/src/main/kotlin/io/tolgee/events/OnEntityPrePersist.ktbackend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.ktbackend/data/src/main/kotlin/io/tolgee/formats/MessagePatternUtil.ktbackend/data/src/main/kotlin/io/tolgee/formats/apple/in/xcstrings/XcstringsFileProcessor.ktbackend/data/src/main/kotlin/io/tolgee/formats/nestedStructureModel/StructureModelBuilder.ktbackend/data/src/main/kotlin/io/tolgee/formats/paramConvertors/in/BaseToIcuPlaceholderConvertor.ktbackend/data/src/main/kotlin/io/tolgee/formats/paramConvertors/in/I18nextToIcuPlaceholderConvertor.ktbackend/data/src/main/kotlin/io/tolgee/formats/paramConvertors/out/BaseToCLikePlaceholderConvertor.ktbackend/data/src/main/kotlin/io/tolgee/formats/paramConvertors/out/IcuToPythonBracePlaceholderConvertor.ktbackend/data/src/main/kotlin/io/tolgee/jobs/migration/allOrganizationOwner/AllOrganizationOwnerJobConfiguration.ktbackend/data/src/main/kotlin/io/tolgee/jobs/migration/translationStats/TranslationStatsJobConfiguration.ktbackend/data/src/main/kotlin/io/tolgee/model/ApiKey.ktbackend/data/src/main/kotlin/io/tolgee/model/AuditModel.ktbackend/data/src/main/kotlin/io/tolgee/model/ForcedServerDateTime.ktbackend/data/src/main/kotlin/io/tolgee/model/LanguageStats.ktbackend/data/src/main/kotlin/io/tolgee/model/Pat.ktbackend/data/src/main/kotlin/io/tolgee/model/QuickStart.ktbackend/data/src/main/kotlin/io/tolgee/model/UserAccount.ktbackend/data/src/main/kotlin/io/tolgee/model/activity/ActivityRevision.ktbackend/data/src/main/kotlin/io/tolgee/service/bigMeta/BigMetaService.ktbackend/data/src/main/kotlin/io/tolgee/service/export/dataProvider/ExportDataProvider.ktbackend/data/src/main/kotlin/io/tolgee/service/invitation/InvitationService.ktbackend/data/src/main/kotlin/io/tolgee/service/key/KeyMetaService.ktbackend/data/src/main/kotlin/io/tolgee/service/key/ResolvingKeyImporter.ktbackend/data/src/main/kotlin/io/tolgee/service/key/utils/KeyInfoProvider.ktbackend/data/src/main/kotlin/io/tolgee/service/language/LanguageService.ktbackend/data/src/main/kotlin/io/tolgee/service/project/ProjectStatsService.ktbackend/data/src/main/kotlin/io/tolgee/service/queryBuilders/LanguageStatsProvider.ktbackend/data/src/main/kotlin/io/tolgee/service/queryBuilders/ProjectStatsProvider.ktbackend/data/src/main/kotlin/io/tolgee/service/queryBuilders/translationViewBuilder/CursorPredicateProvider.ktbackend/data/src/main/kotlin/io/tolgee/service/queryBuilders/translationViewBuilder/QueryGlobalFiltering.ktbackend/data/src/main/kotlin/io/tolgee/service/queryBuilders/translationViewBuilder/StateFilterBuilder.ktbackend/data/src/main/kotlin/io/tolgee/service/queryBuilders/translationViewBuilder/TranslationsViewQueryBuilder.ktbackend/data/src/main/kotlin/io/tolgee/service/translation/AutoTranslationService.ktbackend/data/src/main/kotlin/io/tolgee/service/translation/TranslationService.ktbackend/data/src/main/kotlin/io/tolgee/util/entityPreCommitEventUsageUtil.ktbackend/data/src/main/kotlin/io/tolgee/util/transactionUtil.ktbackend/data/src/main/kotlin/io/tolgee/util/updateStringsInJson.ktbackend/data/src/test/kotlin/io/tolgee/unit/cachePurging/AzureContentStorageConfigCachePurgingTest.ktbackend/data/src/test/kotlin/io/tolgee/unit/cachePurging/BunnyContentStorageConfigCachePurgingTest.ktbackend/data/src/test/kotlin/io/tolgee/unit/cachePurging/CloudflareContentStorageConfigCachePurgingTest.ktbackend/data/src/test/kotlin/io/tolgee/unit/formats/properties/out/PropertiesFileExporterTest.ktbackend/data/src/test/kotlin/io/tolgee/unit/formats/resx/out/ResxExporterTest.ktbackend/data/src/test/kotlin/io/tolgee/unit/formats/xliff/out/XliffFileExporterTest.ktbackend/data/src/test/kotlin/io/tolgee/unit/formats/yaml/out/YamlExportTestData.ktbackend/data/src/test/kotlin/io/tolgee/unit/xlsx/out/XlsxFileExporterTest.ktbackend/development/src/main/kotlin/io/tolgee/facade/InternalPropertiesSetterFacade.ktbackend/ktlint/src/main/kotlin/io/tolgee/testing/ktlint/rules/JakartaTransientInEntities.ktbackend/testing/src/main/kotlin/io/tolgee/fixtures/MachineTranslationTest.ktbackend/testing/src/main/kotlin/io/tolgee/fixtures/MimeMessageParser.ktee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaCheckPreviewWebSocketHandler.ktee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/TaskModelAssembler.ktee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/TaskWithProjectModelAssembler.ktee/backend/app/src/main/kotlin/io/tolgee/ee/component/llm/OpenaiApiService.ktee/backend/app/src/main/kotlin/io/tolgee/ee/component/llm/TolgeeApiService.ktee/backend/app/src/main/kotlin/io/tolgee/ee/development/QaLanguageStatsBranchTestData.ktee/backend/app/src/main/kotlin/io/tolgee/ee/security/thirdParty/SsoDelegateEe.ktee/backend/app/src/main/kotlin/io/tolgee/ee/service/prompt/PromptResultParser.ktee/backend/tests/src/test/kotlin/io/tolgee/ee/WebhookAutomationTest.ktee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/CommunitySuggestionTest.ktee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/activity/ProjectActivityBranchingTest.ktee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/branching/BranchControllerMergingTest.ktee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/task/TaskControllerActivityTest.ktee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/translationMemory/SharedTranslationMemoryControllerTest.ktee/backend/tests/src/test/kotlin/io/tolgee/ee/data/qa/QaPreviewWsSessionStateTest.ktee/backend/tests/src/test/kotlin/io/tolgee/ee/mcp/McpBranchToolsTest.ktee/backend/tests/src/test/kotlin/io/tolgee/ee/projectExportImport/ProjectExportImportImporterTest.kt
💤 Files with no reviewable changes (8)
- backend/app/src/main/kotlin/io/tolgee/component/TolgeeSentryUserProvider.kt
- backend/data/src/main/kotlin/io/tolgee/model/ForcedServerDateTime.kt
- backend/data/src/main/kotlin/io/tolgee/model/QuickStart.kt
- backend/data/src/main/kotlin/io/tolgee/model/LanguageStats.kt
- backend/data/src/main/kotlin/io/tolgee/model/activity/ActivityRevision.kt
- backend/data/src/main/kotlin/io/tolgee/model/ApiKey.kt
- backend/data/src/main/kotlin/io/tolgee/model/Pat.kt
- backend/data/src/main/kotlin/io/tolgee/model/AuditModel.kt
| .setHint( | ||
| "jakarta.persistence.lock.timeout", | ||
| LockOptions.SKIP_LOCKED, | ||
| Timeouts.SKIP_LOCKED_MILLI, | ||
| ).resultList |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -u
rg -n -C 12 '\bgetAllUnlockedChunksForJobs\s*\(' backend --glob '*.kt' || true
rg -n -C 8 'SKIP_LOCKED_MILLI|PESSIMISTIC_WRITE|jakarta\.persistence\.lock\.timeout' \
backend/data/src/main/kotlin/io/tolgee/batch --glob '*.kt' || trueRepository: tolgee/tolgee-platform
Length of output: 13302
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- BatchJobService imports and target method ---'
sed -n '1,70p' backend/data/src/main/kotlin/io/tolgee/batch/BatchJobService.kt
sed -n '320,365p' backend/data/src/main/kotlin/io/tolgee/batch/BatchJobService.kt
printf '%s\n' '--- Caller and surrounding workflow ---'
sed -n '130,215p' backend/data/src/main/kotlin/io/tolgee/batch/BatchJobProjectLockingManager.kt
printf '%s\n' '--- Chunk execution entity and status transitions ---'
rg -n -C 10 'class BatchJobChunkExecution|`@Version`|BatchJobChunkExecutionStatus|status\s*=' \
backend --glob '*.kt' | head -n 260
printf '%s\n' '--- Existing query-lock tests and related method tests ---'
rg -n -C 8 'getAllUnlockedChunksForJobs|SKIP_LOCKED|PESSIMISTIC_WRITE|lock\.timeout|BatchJobProjectLockingManager' \
backend --glob '*Test*.kt' --glob '*.kt' | head -n 320
printf '%s\n' '--- Hibernate/JPA versions ---'
rg -n 'hibernate|jakarta\.persistence|spring-boot' \
build.gradle* settings.gradle* gradle.properties backend --glob '*.gradle*' --glob '*.kts' --glob '*.properties' | head -n 180
printf '%s\n' '--- Static verifier for the target query chain ---'
python3 - <<'PY'
from pathlib import Path
p = Path("backend/data/src/main/kotlin/io/tolgee/batch/BatchJobService.kt")
s = p.read_text()
start = s.index("fun getAllUnlockedChunksForJobs")
end = s.index("\n }", start) + 4
method = s[start:end]
print("target_method_has_setLockMode=", ".setLockMode(" in method)
print("target_method_has_lock_timeout_hint=", '"jakarta.persistence.lock.timeout"' in method)
print("target_method_uses_constructor_projection=", "select new " in method)
print("target_method_result_type=", method.split("):", 1)[1].split("{", 1)[0].strip())
PYRepository: tolgee/tolgee-platform
Length of output: 50378
🌐 Web query:
Hibernate ORM 7 user guide jakarta.persistence.lock.timeout -2 SKIP LOCKED pessimistic lock constructor expression DTO projection setLockMode
💡 Result:
In Hibernate ORM 7, the use of SKIP LOCKED with pessimistic locking is supported and can be managed through both Hibernate-specific mechanisms and standard Jakarta Persistence (JPA) configurations [1][2]. SKIP LOCKED Configuration To achieve a SKIP LOCKED behavior, you can use the Hibernate-specific LockMode.UPGRADE_SKIPLOCKED, which explicitly signals a SELECT... FOR UPDATE SKIP LOCKED statement [2][3]. Alternatively, you can use the combination of a pessimistic lock mode (such as PESSIMISTIC_WRITE) and a specific timeout value of -2, which corresponds to the magic value for SKIP_LOCKED [1][4][5]. In Hibernate 7, Timeouts.SKIP_LOCKED is the preferred mechanism for specifying this timeout value programmatically [4]. The property jakarta.persistence.lock.timeout can also be set to -2 in configuration or query hints to achieve this behavior [1][6][5]. DTO Projections and Locking When using constructor expressions (DTO projections), you are typically performing a read-only projection rather than fetching managed entities [7]. Because DTO projections are not managed entities, they do not participate in the Hibernate persistence context lifecycle [7]. Consequently, applying a lock mode (such as setLockMode) directly to a query returning a DTO projection is generally ineffective or unsupported because the underlying SQL projection (e.g., SELECT new com.example.MyDTO(...)) does not map to a database entity that can be locked via SELECT FOR UPDATE [7]. Pessimistic locking is designed for entities, and if you attempt to use it on a DTO projection, Hibernate cannot translate the lock request into the corresponding FOR UPDATE clause on the projected columns [8][7]. To lock records while retrieving data into a DTO: 1. First, select the entities or their primary keys using a query that applies the desired lock mode (e.g., PESSIMISTIC_WRITE with SKIP_LOCKED) [1][5]. 2. Then, perform the transformation to your DTO using the retrieved entities [7]. Summary of Key APIs in Hibernate 7 - LockMode.UPGRADE_SKIPLOCKED: Provides the explicit lock mode for SKIP LOCKED [2]. - Timeouts.SKIP_LOCKED: The helper constant for the SKIP LOCKED timeout [4]. - SelectionQuery.setLockMode(String alias, LockMode lockMode): Used for alias-specific locking in queries [9]. - SelectionQuery.setHibernateLockMode(LockMode lockMode): Preferred method for applying Hibernate-specific lock modes to a query [10][9]. Note: The legacy LockOptions class is considered obsolete in Hibernate 7 and is moving to an internal SPI package; modern applications should favor the methods on SelectionQuery [10][11].
Citations:
- 1: https://github.com/hibernate/hibernate-orm/blob/master/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc
- 2: https://docs.hibernate.org/orm/7.0/javadocs/org/hibernate/LockMode.html
- 3: https://docs.hibernate.org/orm/7.1/javadocs/org/hibernate/LockMode.html
- 4: https://docs.hibernate.org/orm/7.4/javadocs/org/hibernate/Timeouts.html
- 5: https://stackoverflow.com/questions/41434169/select-for-update-skip-locked-from-jpa-level
- 6: https://jakarta.ee/learn/docs/jakartaee-tutorial/current/persist/persistence-locking/persistence-locking.html
- 7: https://thorben-janssen.com/projections-with-jpa-and-hibernate/
- 8: https://docs.hibernate.org/orm/7.4/javadocs/org/hibernate/LockMode.html
- 9: https://docs.hibernate.org/orm/7.0/javadocs/org/hibernate/query/Query.html
- 10: https://docs.hibernate.org/orm/7.2/javadocs/org/hibernate/LockOptions.html
- 11: https://docs.hibernate.org/orm/7.0/javadocs/org/hibernate/LockOptions.html
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- Exact dependency declarations ---'
rg -n -i --glob '*.gradle' --glob '*.gradle.kts' --glob '*.properties' \
'hibernate-core|hibernateVersion|hibernate.version|spring-boot' \
. | head -n 120
printf '%s\n' '--- Locking manager entry points and call sites ---'
rg -n -C 18 \
'fun (getInitialJobId|tryLock|lock|computeFnBody)|getInitialJobId\(|getAllUnlockedChunksForJobs\(' \
backend/data/src/main/kotlin/io/tolgee/batch/BatchJobProjectLockingManager.kt \
backend --glob '*.kt' | head -n 360
printf '%s\n' '--- Chunk execution reads, claims, and status updates ---'
rg -n -C 12 \
'BatchJobChunkExecution|ChunkExecution|status\s*=\s*BatchJobChunkExecutionStatus|PESSIMISTIC|SKIP_LOCKED|lock\.timeout' \
backend/data/src/main/kotlin/io/tolgee/batch \
backend/data/src/main/kotlin/io/tolgee/model/batch --glob '*.kt' | head -n 420
printf '%s\n' '--- Target method structural verifier ---'
python3 - <<'PY'
from pathlib import Path
p = Path("backend/data/src/main/kotlin/io/tolgee/batch/BatchJobService.kt")
s = p.read_text()
start = s.index("fun getAllUnlockedChunksForJobs")
body = s[start:s.index("\n }", start) + 4]
checks = {
"has_constructor_projection": "select new " in body,
"has_lock_timeout_hint": '"jakarta.persistence.lock.timeout"' in body,
"has_jpa_lock_mode": ".setLockMode(" in body,
"has_hibernate_lock_mode": ".setHibernateLockMode(" in body,
"returns_dto": "List<JobUnlockedChunk>" in body,
}
for k, v in checks.items():
print(f"{k}={v}")
print("--- method ---")
print(body)
PY
printf '%s\n' '--- Relevant test names only ---'
rg -l \
'getAllUnlockedChunksForJobs|BatchJobProjectLockingManager|project.*lock|locked.*chunk|chunk.*lock' \
backend --glob '*Test*.kt' | sortRepository: tolgee/tolgee-platform
Length of output: 50378
🌐 Web query:
site:docs.hibernate.org/orm Hibernate query constructor expression setLockMode pessimistic lock DTO projection
💡 Result:
Applying a pessimistic lock to a query that uses a DTO projection (via constructor expression) is generally not supported or valid in JPA and Hibernate, as pessimistic locks require an entity alias to be locked at the database level [1][2]. When you use a constructor expression (e.g., SELECT NEW com.example.MyDTO(e.name, e.price) FROM Entity e), the query projects scalar values into a DTO [3]. Because these projections do not return managed entities, Hibernate cannot associate the result set with a database row to apply a lock [1][2]. ### Key Considerations: * Pessimistic Locking Requirements: Pessimistic locking (e.g., PESSIMISTIC_WRITE) targets managed entity instances to issue a SELECT FOR UPDATE statement [4]. A DTO projection creates an unmanaged object, rendering it incompatible with this mechanism [1]. * Alias-Specific Locking: In HQL/JPQL, you can specify alias-specific lock modes using SelectionQuery.setLockMode(String alias, LockMode lockMode) [1][2]. However, this requires the alias to refer to a mapped entity in the FROM clause, not a projected DTO [1][2]. * Alternatives: 1. Lock the entities first: If you need to perform operations on entities and return a DTO, fetch and lock the required entity instances first (e.g., using session.find with LockModeType or a separate entity-returning query), then transform them into DTOs in application memory. 2. Native SQL: If locking logic is strictly required for the projection, you may need to use a native SQL query that includes the FOR UPDATE clause manually, though this is database-specific and bypasses Hibernate's abstraction [4]. In modern Hibernate (version 6+), ensure you are using the SelectionQuery interface for all locking configurations rather than the deprecated LockOptions class where applicable [1][2].
Citations:
- 1: https://docs.hibernate.org/orm/7.0/javadocs/org/hibernate/query/Query.html
- 2: https://docs.hibernate.org/orm/current/javadocs/org/hibernate/LockOptions.html
- 3: https://docs.hibernate.org/orm/6.3/querylanguage/html_single/
- 4: https://docs.hibernate.org/orm/5.2/userguide/html_single/chapters/locking/Locking.html
Make locked-chunk detection skip database-locked rows.
getAllUnlockedChunksForJobs uses a DTO constructor projection, so adding a JPA lock mode directly to this query is not a reliable fix. Use an entity-returning or native query with FOR UPDATE SKIP LOCKED, then map the results to JobUnlockedChunk. Otherwise, getInitialJobId can count locked chunks as unlocked and allow another job for the project. Add a concurrency test for this case.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/data/src/main/kotlin/io/tolgee/batch/BatchJobService.kt` around lines
354 - 357, Update getAllUnlockedChunksForJobs to avoid relying on a lock hint
with its DTO constructor projection; use an entity-returning or native query
that applies FOR UPDATE SKIP LOCKED, then map the selected rows to
JobUnlockedChunk. Ensure getInitialJobId cannot treat database-locked chunks as
unlocked, and add a concurrency test covering two jobs contending for the same
project.
| if (simpleStyle != null) { | ||
| sb.append(',').append(simpleStyle) | ||
| } | ||
| sb.append(',').append(simpleStyle) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Preserve the no-style rendering.
simpleStyle returns "" when no style exists. This line therefore renders {0,number} as {0,number,}. Keep the previous style != null check so arguments without a style do not gain a trailing comma.
Proposed fix
- sb.append(',').append(simpleStyle)
+ if (style != null) {
+ sb.append(',').append(simpleStyle)
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| sb.append(',').append(simpleStyle) | |
| if (style != null) { | |
| sb.append(',').append(simpleStyle) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/data/src/main/kotlin/io/tolgee/formats/MessagePatternUtil.kt` at line
426, Update the rendering logic around simpleStyle so the comma is appended only
when style is non-null, preserving `{0,number}` for arguments without a style
while retaining styled output.
| } | ||
|
|
||
| val text = json.get("text")?.asText() ?: "" | ||
| val text = json.get("text")?.asString() ?: "" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -n -C 6 'handleTextUpdate|asString\(\)|asText\(\)|QaCheckPreviewError' ee/backend backendRepository: tolgee/tolgee-platform
Length of output: 50378
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
file='ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaCheckPreviewWebSocketHandler.kt'
echo '--- outline ---'
ast-grep outline "$file" --lang kotlin || true
echo '--- handler ---'
cat -n "$file" | sed -n '1,290p'
echo '--- related files ---'
git ls-files | rg -i 'qa.*(preview|websocket)|websocket.*qa|QaCheckPreview'Repository: tolgee/tolgee-platform
Length of output: 13841
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo '--- exact related test references ---'
rg -n -i -C 8 'QaCheckPreview|qa.?check.?preview|handleTextUpdate|WebSocket' \
ee/backend/tests ee/backend/app/src/test backend 2>/dev/null \
| rg -i -C 8 'QaCheckPreview|qa.?check.?preview|handleTextUpdate|WebSocket' \
| head -n 500
echo '--- Jackson dependency/version declarations ---'
rg -n -i -C 3 'jackson.*databind|tools.jackson|jacksonVersion|jackson.version' \
--glob '!*build*' --glob '!*.lock' --glob '*.gradle*' --glob 'gradle.properties' \
. | head -n 300Repository: tolgee/tolgee-platform
Length of output: 50380
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
file='ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaCheckPreviewWebSocketHandler.kt'
cat -n "$file" | sed -n '1,290p'
git ls-files | rg -i 'qa.*(preview|websocket)|websocket.*qa|QaCheckPreview'
rg -n -i -C 8 'QaCheckPreview|qa.?check.?preview|handleTextUpdate|WebSocket' ee/backend/tests ee/backend/app/src/test 2>/dev/null | head -n 500Repository: tolgee/tolgee-platform
Length of output: 29918
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
test='ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaCheckPreviewWebSocketTest.kt'
helper='ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaPreviewWebSocketTestHelper.kt'
echo '--- test outline and size ---'
wc -l "$test" "$helper"
ast-grep outline "$test" --lang kotlin || true
echo '--- test methods and update assertions ---'
rg -n -C 5 'fun |sendText|sendInit|waitForDone|ERROR|error|message|closeStatus' "$test" "$helper"
echo '--- handler send path ---'
cat -n ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaCheckPreviewWebSocketHandler.kt | sed -n '287,320p'Repository: tolgee/tolgee-platform
Length of output: 47718
🌐 Web query:
Jackson 3.0 JsonNode asString object array throws javadoc
💡 Result:
In Jackson 3.0, the JsonNode.asString() method is designed for coercing scalar values to a String [1][2]. When called on structured types—specifically ObjectNode (JSON objects) or ArrayNode (JSON arrays)—the method throws a tools.jackson.databind.exc.JsonNodeException [3][1]. This behavior differs from typical patterns in Jackson 2.x, where similar methods (like asText()) often returned an empty string or null for structured nodes instead of throwing an exception [4][5]. The JsonNodeException explicitly states that the value type is not coercible to a String [4][5]. To handle nodes that might be structured types without triggering this exception, you should: 1. Check the node type before calling asString() using methods like isTextual(), isObject(), or isArray() [1]. 2. Use non-coercing accessors such as stringValue() if you specifically require a String value and wish to handle non-string nodes differently (e.g., by returning null or a default) [1][6]. If you are migrating code from Jackson 2 to 3, be aware that asString() replaced the deprecated asText() [1][7]. If your existing logic relied on asText() to return a default string for complex objects or arrays, you may need to update your code to explicitly verify the node type or use the appropriate stringValue variants to avoid the JsonNodeException [4][5].
Citations:
- 1: https://github.com/FasterXML/jackson-databind/blob/3.x/src/main/java/tools/jackson/databind/JsonNode.java
- 2: https://javadoc.io/static/tools.jackson.core/jackson-databind/3.0.1/tools.jackson.databind/tools/jackson/databind/node/BaseJsonNode.html
- 3: https://javadoc.io/static/tools.jackson.core/jackson-databind/3.0.0-rc3/tools.jackson.databind/tools/jackson/databind/JsonNode.html
- 4: Failed to upgrade to version 3.0.0 networknt/json-schema-validator#1229
- 5: Coercion bug networknt/json-schema-validator#1246
- 6: Change defaulting of
JsonNode.asXxx(defaultValue)/JsonNode.asXxxOpt()forNullNodeFasterXML/jackson-databind#5558 - 7: https://docs.openrewrite.org/recipes/java/jackson/upgradejackson_2_3_jsonnodemethodrenames
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
test='ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaCheckPreviewWebSocketTest.kt'
helper='ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaPreviewWebSocketTestHelper.kt'
wc -l "$test" "$helper"
rg -n -C 5 'fun |sendText|sendInit|waitForDone|ERROR|error|message|closeStatus' "$test" "$helper"
cat -n "$helper" | sed -n '67,180p'Repository: tolgee/tolgee-platform
Length of output: 48186
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
handler = Path("ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaCheckPreviewWebSocketHandler.kt").read_text()
test = Path("ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaCheckPreviewWebSocketTest.kt").read_text()
helper = Path("ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaPreviewWebSocketTestHelper.kt").read_text()
checks = {
"update calls asString": 'json.get("text")?.asString()' in handler,
"update has no local try/catch": not bool(re.search(
r'private fun handleTextUpdate\b.*?\n\s*}\n\s*\n\s*private fun handleInit',
handler, re.S
)) or 'try {' not in re.search(
r'private fun handleTextUpdate\b.*?\n\s*}\n\s*\n\s*private fun handleInit',
handler, re.S
).group(0),
"handleTextMessage calls update without catch": bool(re.search(
r'override fun handleTextMessage\b.*?handleTextUpdate\(session, state, json\)',
handler, re.S
)),
"tests send only scalar text values": not bool(re.search(
r'send\(\s*(?:mapOf|mutableMapOf).*?"text"\s+to\s+(?!text\b)',
test + helper, re.S
)),
"no non-scalar update test": not bool(re.search(
r'(object|array|non.?scalar|invalid.*text|text.*invalid)',
test, re.I
)),
}
for name, value in checks.items():
print(f"{name}: {value}")
assert all(checks.values())
PYRepository: tolgee/tolgee-platform
Length of output: 348
Handle non-scalar text values in updates.
JsonNode.asString() throws for object and array nodes. Validate text before conversion and return a QaCheckPreviewError. Add tests for object and array values; current tests cover only scalar updates.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/qa/QaCheckPreviewWebSocketHandler.kt`
at line 130, Update the text extraction in the QA preview update handler around
the text JSON field: validate that text is scalar before converting it, and
return a QaCheckPreviewError for object or array values instead of calling
asString() on them. Preserve the existing empty/default behavior and add
coverage for both object and array text updates alongside the existing scalar
tests.
Source: MCP tools
| val output = json?.get("output")?.asString() ?: throw LlmProviderNotReturnedJsonException() | ||
| val contextDescription = json.get("contextDescription")?.asString() |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
# Expect parser tests to cover non-scalar output and contextDescription values.
rg -n -C 4 'PromptResultParser|LlmProviderNotReturnedJsonException|contextDescription|output' ee/backendRepository: tolgee/tolgee-platform
Length of output: 50380
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- parser ---'
cat -n ee/backend/app/src/main/kotlin/io/tolgee/ee/service/prompt/PromptResultParser.kt
printf '%s\n' '--- parser references and tests ---'
rg -n -C 3 'PromptResultParser|LlmProviderNotReturnedJsonException' ee/backend/app ee/backend/tests
printf '%s\n' '--- Jackson dependency declarations ---'
rg -n -C 3 'jackson|tools.jackson' gradle*.kts gradle* libs.versions.toml ee/backend --glob '*.gradle' --glob '*.gradle.kts' --glob '*.toml' --glob '*.kt' | head -250
printf '%s\n' '--- relevant service flow ---'
sed -n '185,225p' ee/backend/app/src/main/kotlin/io/tolgee/ee/service/prompt/PromptServiceEeImpl.ktRepository: tolgee/tolgee-platform
Length of output: 33636
🌐 Web query:
Jackson 3.1.4 JsonNode asString object array behavior JsonNodeException javadoc
💡 Result:
In Jackson 3.x, including version 3.1.4, the JsonNode.asString method is designed to return the string representation of a node if it is a scalar type [1][2]. When called on structured types like ObjectNode (JSON objects) or ArrayNode (JSON arrays), the asString method will throw a JsonNodeException [1][2]. This exception is a standard, unchecked exception introduced in Jackson 3 to handle issues specifically related to JsonNode operations [3][4]. Key behaviors for asString include: - Scalar Types: JSON strings map naturally, and other scalars (like numbers or booleans) are mapped to their string representations [1][2]. - Null and Missing Nodes: JSON null nodes and "missing nodes" are coerced into an empty string ("") [1][2]. - Structured Types: Calling asString on an ArrayNode or ObjectNode triggers a JsonNodeException because these types cannot be coerced into a simple string representation [1][2]. Note that asString is the successor to the Jackson 2.x asText method, which was deprecated in 3.0 as part of a broader effort to unify naming conventions (replacing "Text" with "String" in API method names) [5][6]. If you need to avoid the exception, you can use asString(String defaultValue), which returns the provided default value instead of throwing an exception for non-coercible types [1][2].
Citations:
- 1: https://github.com/FasterXML/jackson-databind/blob/3.x/src/main/java/tools/jackson/databind/JsonNode.java
- 2: https://github.com/FasterXML/jackson-databind/blob/a3a35e49/src/main/java/tools/jackson/databind/JsonNode.java
- 3: Create new exception type
JsonNodeExceptionfor use byJsonNode-related problems FasterXML/jackson-databind#3536 - 4: https://github.com/FasterXML/jackson-databind/blob/master/release-notes/VERSION
- 5: https://docs.openrewrite.org/recipes/java/jackson/upgradejackson_2_3_jsonnodemethodrenames
- 6: Rename
TextNodeasStringNode;JsonNode.xxxTextYyy()(mostly) asJsonNode.xxxStringYyy()[JSTEP-3] FasterXML/jackson-databind#4879
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- provider-response exception ---'
rg -n -C 5 'class LlmProviderNotReturnedJsonException|LlmProviderNotReturnedJsonException' --glob '*.kt' --glob '*.java' .
printf '%s\n' '--- parser-related test files ---'
find ee/backend/tests ee/backend/app -type f \( -name '*Prompt*Test.kt' -o -name '*Parser*Test.kt' \) -print
printf '%s\n' '--- exception handlers ---'
rg -n -C 4 'LlmProviderNotReturnedJsonException|JsonNodeException|JacksonException|ExceptionHandler|ResponseStatus' backend ee --glob '*.kt' --glob '*.java' 2>/dev/null | head -300Repository: tolgee/tolgee-platform
Length of output: 45019
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- exception hierarchy and status ---'
cat -n backend/data/src/main/kotlin/io/tolgee/exceptions/LlmProviderNotReturnedJsonException.kt
rg -n -C 4 'open class FailedDependencyException|class FailedDependencyException|open class ErrorException|class ErrorException' backend/data backend/app --glob '*.kt'
printf '%s\n' '--- generic exception handling ---'
sed -n '150,190p' backend/app/src/main/kotlin/io/tolgee/ExceptionHandlers.kt
rg -n -C 3 '`@ExceptionHandler`\\(Exception::class|`@ExceptionHandler`\\(Throwable::class|Exception::class|Throwable::class' backend/app/src/main/kotlin/io/tolgee --glob '*.kt' | head -120
printf '%s\n' '--- source-level asString implementation ---'
python3 - <<'PY'
from urllib.request import urlopen
url = "https://raw.githubusercontent.com/FasterXML/jackson-databind/3.1/src/main/java/tools/jackson/databind/JsonNode.java"
try:
text = urlopen(url, timeout=10).read().decode()
except Exception as exc:
print(f"fetch failed: {exc}")
else:
lines = text.splitlines()
for i, line in enumerate(lines):
if "String asString" in line:
print("\n".join(f"{j+1}: {lines[j]}" for j in range(max(0, i-4), min(len(lines), i+18))))
PYRepository: tolgee/tolgee-platform
Length of output: 4024
🏁 Script executed:
#!/usr/bin/env bash
set -u
printf '%s\n' '--- exception status definitions ---'
cat -n backend/data/src/main/kotlin/io/tolgee/exceptions/FailedDependencyException.kt
cat -n backend/data/src/main/kotlin/io/tolgee/exceptions/ErrorException.kt | head -80
printf '%s\n' '--- generic exception handlers ---'
rg -n -F '`@ExceptionHandler`(Exception::class)' backend/app/src/main/kotlin/io/tolgee --glob '*.kt' || true
rg -n -F '`@ExceptionHandler`(Throwable::class)' backend/app/src/main/kotlin/io/tolgee --glob '*.kt' || true
printf '%s\n' '--- source-level asString implementation ---'
python3 - <<'PY'
from urllib.request import urlopen
urls = [
"https://raw.githubusercontent.com/FasterXML/jackson-databind/3.1/src/main/java/tools/jackson/databind/JsonNode.java",
"https://raw.githubusercontent.com/FasterXML/jackson-databind/3.x/src/main/java/tools/jackson/databind/JsonNode.java",
]
for url in urls:
print(f"URL: {url}")
try:
text = urlopen(url, timeout=10).read().decode()
except Exception as exc:
print(f"fetch failed: {exc}")
continue
lines = text.splitlines()
found = False
for i, line in enumerate(lines):
if "String asString" in line:
found = True
print("\n".join(f"{j+1}: {lines[j]}" for j in range(max(0, i-5), min(len(lines), i+20))))
if not found:
print("asString declaration not found")
PYRepository: tolgee/tolgee-platform
Length of output: 2487
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
sed -n '260,305p' backend/app/src/main/kotlin/io/tolgee/ExceptionHandlers.ktRepository: tolgee/tolgee-platform
Length of output: 1873
Handle non-scalar provider fields before conversion.
JsonNode.asString() throws JsonNodeException for object and array values. Since PromptServiceEeImpl calls PromptResultParser.parse() outside its RestClientException handler, malformed fields reach the generic Throwable handler and return 500 unexpected_error_occurred instead of the provider-response error. Validate both fields before conversion and add parser tests for object and array values.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@ee/backend/app/src/main/kotlin/io/tolgee/ee/service/prompt/PromptResultParser.kt`
around lines 17 - 18, Update PromptResultParser.parse() to validate output and
contextDescription are scalar JSON values before calling asString(), mapping
object and array values to the existing provider-response error instead of
allowing JsonNodeException to escape. Preserve the current missing-output
behavior, and add parser tests covering object and array values for both fields.
Cleans up the Kotlin compilation warnings left behind by the Spring Boot 4 / Kotlin 2.3 / Java 25 / Hibernate 7 / Jackson 3 upgrade.
388 of 433 warnings are gone across
:data,:api,:ee-app,:development,:server-appand:billing-app— main and test sources. The remaining 45 are all uses of our own deliberately deprecated APIs (Project.userOwner,MtCreditBucket.extraCredits,screenshotUploadedImageIds,createUsersAndOrganizations); suppressing those was deliberately left out of this PR.One commit per fix, so it can be reviewed group by group:
lateinitinitblocks@TemporalTIMESTAMPon ajava.util.Date, which is the default, so column mapping is unchangedasText()/isTextual→asString()/isStringTimeoutsLockOptions.SKIP_LOCKED/NO_WAITmoved toorg.hibernate.Timeouts.*_MILLICriteriaQuery.multiselect→selectcb.constructfor projected views,cb.tuplefor tuple queries,cb.arrayforObject[]; the unnamedJpaCteContainer.withis deprecated too, so the CTE gets an explicit nametext[]mappingStringArrayType/ListArrayType— Hibernate has supported ARRAY attributes since 6onSave/onDelete→onPersist/onRemoveJacksonXmlHttpMessageConverter,CachingConfigurerSupport→CachingConfigurer, Spring Batch 6chunk(size).transactionManager(...)DefaultCredentialsProvider, RedissonKeysScanOptions, commons-lang3RandomStringUtils.secure(), SentryUser.name, Spring DatagetReferenceById, AssertJisCloseTo,java.util.Date.UTC, ktlintchildren20java.lang.Long/Boolean::class.java→::class.javaObjectTypePROPERTY_HIDES_JAVA_FIELDon the pre-commit events, and unchecked casts over erased JPA results / cache entries / reflection / parsed test JSON"in test method names (invalid in Windows report filenames)!!, elvis fallbacks and casts the compiler now proves deadWorth a closer look
Two warnings were hiding real problems rather than noise:
MimeMessageParserpinned JavaMail platform types to non-null, which silently disabled its own null checks even thoughgetRecipientsandgetFromdo return null. The locals are now nullable, so the checks work again.ValidationErrorcast a varargArray<out String>toArray<String>; the property is nowArray<out String>.Two intentional behaviour deltas:
ExceptionHandlers—listOf(parameterName) as List<Serializable>?→listOfNotNull(...), so a null parameter name yields[]instead of[null].MtProviderCatching's?: 100, the twoToIcuPlaceholderConvertorescape paths) — unreachable before and after.The
WebMvcConfigurermessage-converter callbacks deliberately stay on the deprecatedListoverload and are suppressed instead: the replacementServerBuilderAPI bypasses Spring HATEOAS's HAL converter registration.Testing
:data481 tests and:server-app300 tests pass, covering the behaviour-sensitive changes: MFA recovery codes and QuickStart (thetext[]mapping), export and project stats and big meta (themultiselectrewrites), activity (interceptor rename), import, translations view/cursor, format convertors, cache purging and xlsx.Summary by CodeRabbit
Bug Fixes
Security
Refactor