Skip to content

chore: modernize build tooling + fix MariaDB Lock Exception regression#54

Merged
delebedev merged 21 commits into
masterfrom
chore/modernize-build
Jan 22, 2026
Merged

chore: modernize build tooling + fix MariaDB Lock Exception regression#54
delebedev merged 21 commits into
masterfrom
chore/modernize-build

Conversation

@delebedev

@delebedev delebedev commented Jan 10, 2026

Copy link
Copy Markdown
Contributor

Context

Modernizes the build tooling and dependencies to current standards.

Changes

  • Gradle 5.6 → 8.14.3
  • Java 11 → 17
  • Spring Boot 2.2.4 → 3.5.7
  • Spring Framework 5.2.3 → 6.2.12
  • Groovy 2.5 → 4.x
  • Spock 1.3 → 2.4-M5
  • Jakarta EE namespace migration
  • Version 1.7.3 → 2.0.0 (breaking changes)

🔍 Deep Dive: MariaDB Lock Exception Fix

The Problem

After upgrading to Spring 6, the MariaDB integration test should only allow single side-effect for a given action started failing:

Expected: conflictErrorCount == 100
Actual:   conflictErrorCount == 0

The test verifies that concurrent requests for the same idempotent action properly detect lock conflicts and throw ConflictingActionException.

The Investigation 🕵️

Initial hypothesis was that MariaDB's FOR UPDATE NOWAIT semantics were broken. We tested:

  • ❌ MariaDB 10.3 (original)
  • ❌ MariaDB 10.6
  • ❌ MariaDB 11.4

All versions showed the same behavior. NOWAIT was being accepted syntactically but the lock provider wasn't detecting conflicts.

Root Cause Found! 🎯

Spring Framework 6 changed the default SQL exception translator.

Spring Version Default Translator Behavior
Spring 5.x SQLErrorCodeSQLExceptionTranslator Maps vendor error codes (e.g., MySQL 1205) to Spring exceptions
Spring 6.x SQLExceptionSubclassTranslator Uses JDBC exception subclasses, ignores vendor codes

This meant MariaDB error 1205 (lock wait timeout) was being translated to UncategorizedSQLException instead of CannotAcquireLockException:

// Before fix (Spring 6 default):
CAUGHT: org.springframework.jdbc.UncategorizedSQLException
  SQL state [HY000]; error code [1205]; Lock wait timeout exceeded

// After fix:
CAUGHT: CannotAcquireLockException
  Lock wait timeout exceeded

The JdbcMariaDbLockProvider catches CannotAcquireLockException to detect lock conflicts - with the wrong exception type, it was throwing instead of returning Optional.empty().

The Fix

Explicitly configure SQLErrorCodeSQLExceptionTranslator in the lock provider:

public JdbcMariaDbLockProvider(JdbcTemplate jdbcTemplate) {
    // Spring 6 switched to SQLExceptionSubclassTranslator by default,
    // which doesn't use vendor-specific error codes. We need
    // SQLErrorCodeSQLExceptionTranslator to properly translate
    // MariaDB error 1205 (lock wait timeout) to CannotAcquireLockException.
    DataSource dataSource = jdbcTemplate.getDataSource();
    JdbcTemplate localJdbcTemplate = new JdbcTemplate(dataSource);
    if (dataSource != null) {
        localJdbcTemplate.setExceptionTranslator(
            new SQLErrorCodeSQLExceptionTranslator(dataSource));
    }
    this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(localJdbcTemplate);
}

Migration Notes

Services using idempotence4j-mariadb with Spring Boot 3.x / Spring Framework 6.x should upgrade to version 2.0.0 to get the corrected exception translation behavior.

Impact of the bug: Wrong exception type (UncategorizedSQLException instead of ConflictingActionException). Lock acquisition and timeout behavior are NOT affected.

Test Plan

  • All MariaDB integration tests pass
  • All PostgreSQL integration tests pass
  • Unit tests pass
  • Build succeeds with Java 17

🤖 Investigated and fixed with Claude Code 🔬

Copilot AI review requested due to automatic review settings January 10, 2026 19:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Changes: Maintenance (1)

This PR modernizes the build tooling by upgrading Gradle to 8.14.3, Spring Boot to 3.5.7, and updating several key dependencies including Lombok, Groovy, and Spock. It also updates the Java target to 17 and adds JUnit Platform support for tests.

Changes:

  • Upgraded Gradle wrapper from 7.3.3 to 8.14.3
  • Updated Spring Boot from 2.2.4 to 3.5.7 and Spring JDBC from 4.3.18 to 6.2.12
  • Migrated Groovy from 2.5.13 (Codehaus) to 4.0.29 (Apache) and Spock from 1.3 to 2.4-M5
  • Updated Java compatibility from 1.9 to 17 and added JUnit Platform launcher for test execution
  • Added docker-compose executable auto-detection for macOS Homebrew and Linux installations

Reviewed changes

Copilot reviewed 9 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
libraries.gradle Updated dependency versions for Lombok, Spring Boot, Spring JDBC, Groovy, and Spock; added groovy-json, groovy-sql, and junit_platform_launcher dependencies
build.gradle Updated Gradle version, Java target to 17, Spring Boot plugin to 3.5.7, dependency-management plugin to 1.1.7, added useJUnitPlatform() for tests, and docker-compose executable auto-detection
idempotence4j-test/build.gradle Added groovy-json and groovy-sql dependencies for Groovy test support
idempotence4j-postgres/build.gradle Added groovy-sql dependency for integration tests
idempotence4j-mariadb/build.gradle Added groovy-sql dependency for integration tests and fixed indentation
idempotence4j-benchmarks/build.gradle Changed bootJar classifier property from deprecated 'classifier' to 'archiveClassifier'
gradlew Updated Gradle wrapper script to the version shipped with Gradle 8.14.3 with improved POSIX compliance and argument handling
gradle/wrapper/gradle-wrapper.properties Updated Gradle distribution URL from 7.3.3 to 8.14.3
gradle/wrapper/gradle-wrapper.jar Updated Gradle wrapper JAR binary to match version 8.14.3

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread libraries.gradle
delebedev and others added 3 commits January 10, 2026 19:23
Spring 6 changed the default SQL exception translator from SQLErrorCodeSQLExceptionTranslator
to SQLExceptionSubclassTranslator. This caused MariaDB error 1205 (lock wait timeout) to be
translated to UncategorizedSQLException instead of CannotAcquireLockException, breaking the
lock acquisition logic in JdbcMariaDbLockProvider.

The fix creates a new JdbcTemplate with the correct exception translator instead of mutating
the passed instance. This avoids side effects on shared JdbcTemplate instances while
preserving transaction safety - Spring's DataSourceTransactionManager binds connections at
the DataSource level, so both the original and new JdbcTemplate participate in the same
transaction and use the same database connection.

Co-Authored-By: Claude <noreply@anthropic.com>
@delebedev delebedev force-pushed the chore/modernize-build branch from 8048414 to 963825b Compare January 10, 2026 21:39
delebedev and others added 4 commits January 10, 2026 22:27
- SLF4J 1.7.26 → 2.0.17 (fixes Logback conflict)
- Jackson 2.13.2 → 2.18.2
- Micrometer 1.3.5 → 1.14.5
- HikariCP 3.3.1 → 6.2.1
- Flyway 6.1.4 → 11.3.4
- PostgreSQL driver 42.2.16 → 42.7.5
- MariaDB driver 2.5.2 → 3.5.2

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@delebedev delebedev force-pushed the chore/modernize-build branch from f6d19ea to 81668f0 Compare January 10, 2026 22:41
delebedev and others added 2 commits January 10, 2026 22:51
Flyway 10.x moved database-specific support to separate modules.
MariaDB support requires the flyway-mysql artifact.

Co-Authored-By: Claude <noreply@anthropic.com>
Flyway 10.x moved database-specific support to separate modules.
PostgreSQL support requires the flyway-database-postgresql artifact.

Also updated CI from Postgres 10.9 to 12 (matching docker-compose).

Co-Authored-By: Claude <noreply@anthropic.com>
@delebedev

Copy link
Copy Markdown
Contributor Author

/request-review

@platon-github-app-production

Copy link
Copy Markdown

Unfortunately, the review request could not be sent to any teams:

  • No teams were found to request a review. If a review has already started (someone has already approved or added at least one comment), you need to explicitly specify the team you want to request the review from: /request-review <team-1-name> <team-2-name>.

@delebedev delebedev changed the title chore: modernize build tooling chore: modernize build tooling + fix MariaDB Lock Exception regression Jan 12, 2026
delebedev and others added 2 commits January 12, 2026 09:34
Breaking changes warrant a major version bump:
- Java 11 → 17
- Spring Boot 2 → 3
- Spring Framework 5 → 6
- Jakarta EE namespace migration

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
// because Spring's DataSourceTransactionManager binds connections at the DataSource
// level, not the JdbcTemplate level - both templates will use the same connection
// within a transaction.
DataSource dataSource = jdbcTemplate.getDataSource();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think it'd be simpler to make the constructor take a DataSource directly instead of a JdbcTemplate instance, the auto configuration bean method already takes in a data source instance so should be easy enough.

Technically it'd be a breaking change, though given JdbcMariaDbLockProvider is being created by auto config, it feels unlikely to cause real issues for users.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this suggestion, breaking change could be justified too - it's 2.0.0 + this bugfix requires careful implementation

@brettyukich

Copy link
Copy Markdown

Here are the release notes for the SQLExceptionSubclassTranslator change

https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-6.0-Release-Notes#data-access-and-transactions

Comment thread build.gradle
classpath "io.spring.gradle:dependency-management-plugin:1.0.10.RELEASE"
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.2.4.RELEASE"
classpath "io.spring.gradle:dependency-management-plugin:1.1.7"
classpath "org.springframework.boot:spring-boot-gradle-plugin:3.5.7"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're upgrading all the things and making breaking changes, how much trouble would it be to test this against both Spring Boot 3.5.x and Spring Boot 4? There were quite a few changes (also changes in Spring Framework 7) that are worth testing against.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this doesn't use any of wise provided infra it's a bit harder but not impossible

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

delebedev and others added 6 commits January 22, 2026 09:44
Move dependency management to allprojects block and let Spring Boot BOM
manage spring-jdbc and junit-platform-launcher versions to ensure
compatibility across Boot versions.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Replace Spring's SQLErrorCodeSQLExceptionTranslator with direct JDBC
error code checking for lock conflict detection. This reduces coupling
to Spring's internal exception mapping which changed in Spring 6.

Breaking change: JdbcMariaDbLockProvider constructor now takes DataSource
instead of JdbcTemplate.

See docs/adr/0001-low-level-jdbc-for-lock-detection.md for details.

Co-Authored-By: Claude <noreply@anthropic.com>
Remove deprecated spring.datasource.platform and initialization-mode
properties in favor of spring.sql.init.mode.

Co-Authored-By: Claude <noreply@anthropic.com>
Remove explicit classes list from @SpringBootTest to let Spring Boot
auto-configure DataSource and other required beans properly.

Co-Authored-By: Claude <noreply@anthropic.com>
- Add AutoConfiguration.imports file for Spring Boot 3+ style registration
- Add spring-boot-starter-jdbc to test dependencies for DataSource auto-config
- Update test application.yml to use spring.sql.init.mode instead of deprecated properties
- Simplify test @SpringBootTest annotation

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
normanma-tw
normanma-tw previously approved these changes Jan 22, 2026
@wise-github-bot-app

Copy link
Copy Markdown

The approval(s) from normanma-tw do(es)n't fullfill the approvers requirements because:

  • The approver's cost centre, ENGPLA, maps to the PLATFORM business function. As the code that was changed is owned by ENGINEERING, this approval won't satisfy our separation of duties check. We'll need an additional approval from someone in ENGINEERING. This approval may still help satisfy other codeowner requirements.

pturovsk
pturovsk previously approved these changes Jan 22, 2026
Rename matrix job to test-matrix and add a build job that depends on it.
This ensures branch protection can target the single 'build' check that
only passes when all matrix variants succeed.

Also split publish into a separate job that runs only on master.

Co-Authored-By: Claude <noreply@anthropic.com>
@delebedev delebedev dismissed stale reviews from pturovsk and normanma-tw via 5498a4a January 22, 2026 12:49
@delebedev delebedev merged commit 946c811 into master Jan 22, 2026
10 checks passed
@delebedev delebedev deleted the chore/modernize-build branch January 22, 2026 13:07
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.

6 participants