Releases run in CI via the Release GitHub Actions workflow (manual workflow_dispatch
trigger). Only allowlisted users may trigger it.
- Go to Actions → Release → Run workflow.
- Enter:
- releaseVersion — the version to release, e.g.
0.5.1(no-SNAPSHOT). - nextVersion — the next development version, e.g.
0.5.2(no-SNAPSHOT; the workflow appends it).
- releaseVersion — the version to release, e.g.
- Run it. The workflow checks you are on the allowlist, creates branch
release/v<version>, sets the release version and commits + tagsv<version>on it (crediting you as co-author), publishes all modules to GitHub Packages, bumps to<nextVersion>-SNAPSHOT, pushes the branch + tag, creates the GitHub Release, then opens a PR (release/v<version>→master) and enables auto-merge. The PR merges automatically once the required build check passes.
The trigger allowlist is hardcoded in .github/workflows/release.yml as
RELEASE_ALLOWED_ACTORS (space-separated GitHub usernames); edit it via a normal PR.
One-time setup (maintainer):
- Install a GitHub App on the repo with Contents: write and Pull requests: write,
and store its credentials as repo secrets
RELEASE_APP_IDandRELEASE_APP_PRIVATE_KEY. The release PR is created under this App so it triggers CI (a PR created by the default token would not, and auto-merge would hang). - Enable Settings → General → Allow auto-merge.
- Keep the build check required on
masterbranch protection — this is the gate auto-merge waits on.
publish-java.yml ("Publish artifact (manual re-publish)") is a manual-only escape hatch for
re-publishing an already-released version; it does not bump or tag.
Important
Required property:
spring.mvc.problemdetails.enabled: false
Generic exceptions are provided. If they are thrown, application should return response compatible with ProblemDetail RFC 7807 standard.
Throw or extend those RuntimeExceptions:
| Exception | Response status |
|---|---|
| BadRequestException | 400 |
| ResourceCreationException | 400 |
| ResourceNotFoundException | 404 |
| ResourceConflictException | 409 |
| InternalServerErrorException | 500 |
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "Test message",
"instance": "/exceptions/bad-request",
"timestamp": "2024-12-03T10:31:09.840944Z"
}{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "Constraint Violation",
"instance": "/exceptions/validation/method-argument-not-valid",
"violations": [
{
"field": "age",
"message": "must be greater than or equal to 18"
}
],
"timestamp": "2024-12-03T12:55:48.119636Z"
}You can use @FixedClock annotation to mock clock in tests. Example usage:
@FixedClock
@TestToolsIntegrationTest
class FixedClockIT {
@Autowired
Clock clock;
@Test
void givenClassLevelAnnotation_whenUseClock_thenReturnMockedDateTime() {
// Given
final var now = Instant.now(clock);
Assertions.assertThat(now).isEqualTo(Instant.parse(FixedClock.DEFAULT_LOCAL_INSTANT));
}
@FixedClock("2027-01-10T10:00:00Z")
@Test
void givenMethodLevelAnnotation_whenUseClock_thenOverrideMockedDateTime() {
// Given
final var now = Instant.now(clock);
Assertions.assertThat(now).isEqualTo(Instant.parse("2027-01-10T10:00:00Z"));
}
}Extend your test class with @ExtendWith(LogbackVerifierExtension.class), to enable support for customized log checks. Refer to LogVerifier class.
@ExtendWith(LogbackVerifierExtension.class)
class Test {
@Test
void test(LogVerifier logVerifier) {
whatever();
logVerifier.containsInfo("desired log");
}
}@ClearCollections("accounts")
class TestIT {
@Test
void test() {
// ...
}
}Should produce following log message after each test "Clearing up [accounts] collections. Removed X entries"
@ClearCollections(value = "accounts", drop = true)
class TestIT {
@Test
void test() {
// ...
}
}Should produce following log message after each test
Dropped collection accounts
@ExtendWith(MetricsVerifierExtension.class)
class MetricsIT {
@Test
void shouldVerifyMetricValue(MetricsVerifier metricsVerifier) {
meterRegistry.counter("dummy.metric").increment();
meterRegistry.counter("dummy.metric").increment();
metricsVerifier.assertMetric("dummy.metric")
.hasValueGreaterThan(1.0)
.hasValueGreaterThanOrEqualTo(2.0)
.hasValue(2.0);
}
}TraceVerifier - Validate distributed tracing
@ExtendWith(TraceVerifierExtension.class)
class TracingIT {
@Test
void testTracing(TraceVerifier verifier) {
String traceId = verifier.getCurrentTraceId();
verifier.assertTraceIdValid(traceId);
// Verify response has trace headers
ResponseEntity<?> response = restTemplate.getForEntity("/api", String.class);
verifier.assertResponseHasTraceHeaders(response);
// Verify trace propagation
String parentTraceId = "aaaabbbbccccdddd1111222233334444";
String childTraceId = response.getHeaders().getFirst("Trace-Id");
verifier.assertTracePropagated(parentTraceId, childTraceId);
}
}Application automatically logs information around requests.
for instance:
Incoming: method: GET, uri: /api/logs, queryString: ?queryParam=testParam, remoteIp: 127.0.0.1, userAgent: null
[...]
Request completed. Status: 200, duration: 2ms.
It also automatically logs collection size if that's the response body.
Collection returned on endpoint: /api/logs/withCollection, size: 2, status: 200
- Automatic common tags on all metrics
- Trace response headers (Trace-Id, Span-Id) + W3C propagation
- Resource attributes (service.name, namespace, environment)
Configuration: chassis.observability.*
Full documentation: chassis-tools/docs/OBSERVABILITY.md
- Release workflow now restricts triggering to an allowlist, and after publishing opens a
release/v<version>→masterPR with auto-merge (created via a GitHub App so CI runs). The triggerer is credited in the PR body and as commit co-author.
- Replaced
prepare-release.sh/tag-release.shwith a CI-drivenworkflow_dispatchRelease workflow;publish-java.ymlis now a manual-only re-publish. Fixes the tag landing on the wrong commit and the tag-push-does-not-publish gap.
- Updated GitHub Actions versions in the CI workflows:
actions/checkoutv4 -> v6,actions/setup-javav4 -> v5, andactions/cache(restore/save) v4 -> v5. - Added
spring-boot-configuration-processorto themaven-compiler-pluginannotationProcessorPathsin both the rootchassispom andchassis-parent. OnceannotationProcessorPathsis declared, Maven discovers processors only from that list, so the Spring Boot processor was being suppressed and nospring-configuration-metadata.jsonwas generated for@ConfigurationPropertiesclasses (e.g.chassis.observability.*). Metadata is now produced. - Upgraded Spring Boot 4.0.3 -> 4.0.6 (root pom,
chassis-bom,chassis-parentproperty andspring-boot-starter-parent); kept Spring Cloud 2025.1.1 (latest patch on the SB-4.0-compatible line). - Aligned versions to the Spring Boot 4.0.6 BOM by removing redundant
chassis-bompins it already manages at the same version (Lombok, junit:junit, assertj-core, testcontainers-mongodb, h2) and dropping the locallombok.versionproperty inchassis-parent. - JUnit now follows the SB-managed junit-bom 6.0.3 (removed the
chassis-tools-testjunit-bom 5.14.4 override) and archunit-junit5 is unified at 1.4.1 (removed the divergent 1.4.0 pin). Kept intentionally-newer pins (mockito-core 5.21.0, maven-surefire/failsafe 3.5.6, git-commit-id 9.2.0) to avoid downgrades.
- Migrated to Java 25 (Spring Boot 4.0.3, Spring Cloud 2025.1.1). CI runners and the
Dockerfilebase image (eclipse-temurin:21.0.5_11-jre->eclipse-temurin:25.0.3_9-jre) updated to JDK 25. - Bumped dependencies and plugins for JDK 25 compatibility: spring-cloud-contract-maven-plugin 4.3.1 -> 5.0.2, maven-surefire/maven-surefire-report/maven-failsafe 3.5.4 -> 3.5.6, maven-release-plugin 3.1.1 -> 3.3.1, git-commit-id-maven-plugin 9.0.2 -> 9.2.0, maven-jxr-plugin 3.3.2 -> 3.6.0, Lombok 1.18.42 -> 1.18.46, Guava 33.5.0-jre -> 33.6.0-jre, junit-bom 5.12.2 -> 5.14.4, embedded-mongo 4.11.0 -> 4.22.0, testcontainers-mongodb 2.0.3 -> 2.0.5.
- Declared Lombok on the maven-compiler-plugin
annotationProcessorPathsin both the rootchassispom andchassis-parent. JDK 23+ no longer runs annotation processors discovered only from the classpath, so without this@Slf4j/@RequiredArgsConstructorproduced nologfield or constructor.
- Extracted observability into new
chassis-starter-observabilitymodule with minimal dependencies (actuator + micrometer + opentelemetry). Removed unusedDistributedTracingProperties.
- Added
ErrorHandlingDemoControllerand slice IT inchassis-test-appshowcasing every failure mode handled bychassis-starter-errorhandling.
- Extracted errorhandling into new
chassis-starter-errorhandlingmodule with minimal dependencies (web + validation only).
-
Added observability test utilities in
chassis-tools-test:- Added
MetricsVerifierExtension - Added
TraceVerifierExtension
- Added
-
Automatic export of metric/logs/traces with agent
- Metrics: Automatic common tags and resource attributes
- Tracing: Response header exposure (Trace-Id, Span-Id), W3C propagation, sampling controls
- Auto Instrumentation: Spring MVC, RestTemplate, WebClient (via OpenTelemetry Java Agent)
- Springboot 3.5.10 -> 4.0.3
- Spring Cloud 2025.0.1 -> 2025.1.1
- Undertow -> Tomcat
- Springboot 3.4.4 -> 3.5.10
- Added support for logging around requests
- Dropped support for previous logging configuration under
budget.chassis.http-logging.*.
Added @ClearCollections test annotation and docker-compose support for test app only.
Added @FixedClock and LogbackVerifierExtension to test fixtures.
- It's not necessary for now, maybe it will come back in the future.
- SpringBoot 3.4.4
- Java 21
- updated to java 21 and mvn wrapper to 3.9.9
- Dockerized test app
- Reimplemented first cross cutting concern Exception handling with problem detail response
- chassis-dependencies -> all compile dependencies for microservice
- chassis-bom -> bill of materials
- renamed module chassis-test-tools -> chassis-tools-test
- chassis-test-app -> module with application for tests.
- chassis-tools -> single module with cross cutting concern functionalities
Unit tests and integration tests are executed separately. Integration tests need to end with *IT suffix.
Removed external microservices verification from workflow
- Dependency management for lombok in bom.
- Jacoco verification during verify stage instead of test
- added @ControllerTest annotation to use in tests
- Springboot 3.1.0 -> 3.2.1
- junit-5 5.9.3 -> 5.10.1
- updated mockito and assertj
- spring-cloud-dependencies 2022.0.3 -> 2023.0.0
- cucumber 7.6.0 -> 7.15.0
- Decommission password-management support in CI/CD
- Springboot 3.0.4 -> 3.1.0
- junit-5 5.9.2 -> 5.9.3
- spring-cloud-dependencies 2022.0.1 -> 2022.0.3
- Dependency management for test containers.
- Added missing auto configurations
- Removed dummy RestTools.java
- Springboot 2.7.8 -> 3.0.4
- Added @ChassisApplication annotation. Should replace @SpringBootApplication
- Renamed chassis-settings module to chassis-bom (breaking change)
- Renamed Maven properties: chassis.dependencies.springboot.version -> chassis.springboot.version, chassis.dependencies.springcloudtrain.version -> chassis.springcloudtrain.version, chassis.dependencies.springcloud.version -> chassis.springcloud.version, chassis.dependencies.openapi.version -> chassis.openapi.version (breaking change)
- Dropped distributed tracing support.
- Upgrade of various dependencies
- Replaced embedded mongo dependency (as in spring migration guide)
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>3.5.1</version>
</dependency>->
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring30x</artifactId>
<version>4.6.2</version>
</dependency>- Springboot 2.4.9 -> 2.7.1 (one by one).
- Many dependencies upgrade.
- New parent for API modules.
- Extended actuator info endpoint. Example:
{
"git": {
"branch": "develop",
"commit": {
"id": "ac9e2d7",
"time": "2021-09-16T13:00:18Z"
}
},
"build": {
"springbootVersion": "2.4.9",
"version": "0.7.0-SNAPSHOT",
"artifact": "budget-management",
"name": "budget-management",
"time": "2021-12-15T18:57:44.209Z",
"chassisVersion": "develop",
"group": "pl.konradboniecki.budget"
}
}- Springboot 2.4.9
- Springboot 2.3.2.RELEASE