Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ This repo holds the Go reference implementation and its ports (`java/`, `js/`, `
- **Same tests everywhere**: give each affected implementation an equivalent test for the behavior, on every database it supports.
- **Document intended differences**: an API should be idiomatic for its language, but observable behavior should match Go. Record any difference that remains in the port's differences doc (for example `java/DIFFERENCES.md`).

## Commit and PR Titles

- When a change affects only a subset of the languages, prefix the PR title and commit subjects with the affected languages in lowercase brackets: `[java]`, `[go]`, or `[go, ruby]`.
- Use `go`, `java`, `js`, `ruby`, and `rust` as the language names. List multiple languages alphabetically, separated by a comma and a space.
- Avoid repeating the language names in the rest of the title when the prefix already makes the scope clear. For example, use `[java] Enforce warning-free builds` instead of `[java] Enforce warning-free Java builds`.
- Omit the prefix for changes that affect all languages or are independent of any particular language, such as repository-wide contributor guidance.

## Package Naming and Organization

- Package names are lowercase, short, and representative; avoid `common`, `util`, or overly broad names.
Expand Down
16 changes: 9 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

SQLC ?= sqlc
MVN ?= mvn
java_mvn = ./java/bin/check-maven-warnings.sh $(MVN) --batch-mode --no-transfer-progress -Dstyle.color=never

.PHONY: check/modzip
check/modzip: ## Check that no Go module zip includes fixtures, testdata, or another language's port
Expand Down Expand Up @@ -117,12 +118,12 @@ lint/rust: ## Run Rust formatting and clippy checks, including single-backend bu
# Java targets stay separate so Go-only contributors do not need a JDK or Maven.
.PHONY: build/java
build/java: ## Build the Java library, CLI, and development adapter (JDK 21+)
$(MVN) --batch-mode --no-transfer-progress -f java/pom.xml package -DskipTests
$(java_mvn) -f java/pom.xml package -DskipTests

.PHONY: lint/java
lint/java: ## Compile Java, check formatting, and lint Go maintenance tools
lint/java: lint/java/tools
$(MVN) --batch-mode --no-transfer-progress -f java/pom.xml verify -DskipTests
$(java_mvn) -f java/pom.xml verify -DskipTests

# Java's module boundary excludes it from Go package discovery. Pass the tool
# files explicitly so they use this workspace's Go version and test dependencies.
Expand Down Expand Up @@ -172,27 +173,28 @@ RUST_POSTGRES_TESTS_ENV = RUSTFLAGS="$$RUSTFLAGS --cfg river_postgres_tests" \
test/java: ## Run Java tests, executable CLI tests, and formatting checks
test/java: generate/fixtures verify/java-migrations
test/java: test/java/tools
$(MVN) --batch-mode --no-transfer-progress -f java/pom.xml verify
$(java_mvn) -f java/pom.xml verify

# Fixture comparisons use temporary SQLite databases, with no Postgres or legacy adapter.
.PHONY: test/java/conformance
test/java/conformance: ## Run Java tests that check Go-generated conformance fixtures
test/java/conformance: generate/fixtures
$(MVN) --batch-mode --no-transfer-progress -f java/pom.xml -pl river test -Dgroups=conformance -Driver.test.database=sqlite
$(java_mvn) -f java/pom.xml -pl river test -Dgroups=conformance -Driver.test.database=sqlite

.PHONY: test/java/postgres
test/java/postgres: ## Run Java tests with Postgres client and worker coverage (requires RIVER_TEST_DATABASE_URL)
test/java/postgres: generate/fixtures verify/java-migrations
@test -n "$$RIVER_TEST_DATABASE_URL" || { echo "RIVER_TEST_DATABASE_URL is required" >&2; exit 1; }
$(MVN) --batch-mode --no-transfer-progress -f java/pom.xml verify -Driver.test.database=postgres
$(java_mvn) -f java/pom.xml verify -Driver.test.database=postgres

.PHONY: test/java/sqlite
test/java/sqlite: ## Run Java unit, SQLite, and executable CLI tests without Postgres
test/java/sqlite: generate/fixtures verify/java-migrations
$(MVN) --batch-mode --no-transfer-progress -f java/pom.xml verify -Driver.test.database=sqlite -DexcludedGroups=postgres
$(java_mvn) -f java/pom.xml verify -Driver.test.database=sqlite -DexcludedGroups=postgres

.PHONY: test/java/tools
test/java/tools: ## Test Java's Go maintenance tools
test/java/tools: ## Test Java maintenance tools and Maven warning enforcement
bash java/bin/check-maven-warnings_test.sh
go test ./java/bin/check-packages/*.go
go test ./java/bin/sync-migrations/*.go

Expand Down
14 changes: 14 additions & 0 deletions java/bin/check-maven-warnings.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Maven 3 has no general fail-on-warning option. Keep its output visible, then
# reject warnings from packaging and other plugins as well as the compiler.
set -euo pipefail

maven_log=$(mktemp)
trap 'rm -f "$maven_log"' EXIT

"$@" 2>&1 | tee "$maven_log"

if grep -q '^\[WARNING\]' "$maven_log"; then
echo 'Java build emitted Maven warnings; fix them or document an intentional suppression.' >&2
exit 1
fi
31 changes: 31 additions & 0 deletions java/bin/check-maven-warnings_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail

script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
test_dir=$(mktemp -d)
trap 'rm -rf "$test_dir"' EXIT

check() {
local name=$1 expected=$2 output=$3 message=$4
local actual=0
"$script_dir/check-maven-warnings.sh" bash -c "$output" >"$test_dir/output" 2>&1 || actual=$?
if [[ "$actual" -ne "$expected" ]]; then
cat "$test_dir/output" >&2
echo "$name: expected exit $expected, got $actual" >&2
exit 1
fi
if ! grep -Fq "$message" "$test_dir/output"; then
echo "$name: missing command output: $message" >&2
exit 1
fi
}

check clean 0 'echo "[INFO] BUILD SUCCESS"' '[INFO] BUILD SUCCESS'
check warning_stdout 1 'echo "[WARNING] duplicate class"' '[WARNING] duplicate class'
check warning_stderr 1 'echo "[WARNING] duplicate resource" >&2' '[WARNING] duplicate resource'
check command_failure 23 'echo "[ERROR] build failed"; exit 23' '[ERROR] build failed'
check failure_with_warning 17 'echo "[WARNING] warning before failure"; exit 17' '[WARNING] warning before failure'
check ordinary_word 0 'echo "[INFO] test checks WARNING handling"' '[INFO] test checks WARNING handling'
check application_log 0 'echo "WARNING: expected test log"' 'WARNING: expected test log'

echo 'Maven warning enforcement checks passed'
8 changes: 6 additions & 2 deletions java/cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>true</shadedArtifactAttached><shadedClassifierName>all</shadedClassifierName>
<transformers>
<!-- Preserve all dependency attribution text when resource paths overlap. -->
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/LICENSE</resource></transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/NOTICE</resource></transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.riverqueue.cli.MigrationCli</mainClass>
<manifestEntries><Implementation-Version>${project.version}</Implementation-Version><Enable-Native-Access>ALL-UNNAMED</Enable-Native-Access></manifestEntries>
<manifestEntries><Multi-Release>true</Multi-Release><Implementation-Version>${project.version}</Implementation-Version><Enable-Native-Access>ALL-UNNAMED</Enable-Native-Access></manifestEntries>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<filters><filter><artifact>*:*</artifact><excludes><exclude>META-INF/*.SF</exclude><exclude>META-INF/*.DSA</exclude><exclude>META-INF/*.RSA</exclude><exclude>module-info.class</exclude><exclude>META-INF/versions/*/module-info.class</exclude></excludes></filter></filters>
<!-- The manifest transformer writes the output before input filtering. -->
<filters><filter><artifact>*:*</artifact><excludes><exclude>META-INF/MANIFEST.MF</exclude><exclude>META-INF/*.SF</exclude><exclude>META-INF/*.DSA</exclude><exclude>META-INF/*.RSA</exclude><exclude>module-info.class</exclude><exclude>META-INF/versions/*/module-info.class</exclude></excludes></filter></filters>
</configuration></execution></executions>
</plugin>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-failsafe-plugin</artifactId><version>3.5.4</version>
Expand Down
25 changes: 24 additions & 1 deletion java/conformance/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,28 @@
<dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId></dependency>
<dependency><groupId>org.xerial</groupId><artifactId>sqlite-jdbc</artifactId></dependency>
</dependencies>
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.6.1</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals><configuration><createDependencyReducedPom>false</createDependencyReducedPom><transformers><transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/><transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>com.riverqueue.conformance.Adapter</mainClass></transformer></transformers><filters><filter><artifact>*:*</artifact><excludes><exclude>META-INF/*.SF</exclude><exclude>META-INF/*.DSA</exclude><exclude>META-INF/*.RSA</exclude></excludes></filter></filters></configuration></execution></executions></plugin></plugins></build>
<build><plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.6.1</version>
<executions><execution><phase>package</phase><goals><goal>shade</goal></goals><configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<!-- Preserve all dependency attribution text when resource paths overlap. -->
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/LICENSE</resource></transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/NOTICE</resource></transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.riverqueue.conformance.Adapter</mainClass>
<manifestEntries><Multi-Release>true</Multi-Release></manifestEntries>
</transformer>
</transformers>
<!-- The manifest transformer writes the output before input filtering. -->
<filters><filter><artifact>*:*</artifact><excludes>
<exclude>META-INF/MANIFEST.MF</exclude>
<exclude>META-INF/*.SF</exclude><exclude>META-INF/*.DSA</exclude><exclude>META-INF/*.RSA</exclude>
<exclude>module-info.class</exclude><exclude>META-INF/versions/*/module-info.class</exclude>
</excludes></filter></filters>
</configuration></execution></executions>
</plugin>
</plugins></build>
</project>
19 changes: 19 additions & 0 deletions java/docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,25 @@ filtered to changes in Java, its build configuration, fixtures, and canonical
migrations. Go changes run the smaller fixture suite in the shared Conformance
workflow alongside Rust and JavaScript.

## Warning policy

Java builds keep `-Xlint:all` enabled and fail on unsuppressed compiler warnings,
including test compilation. Tests that intentionally hold workers or subscriptions
open for a block use a method-level `@SuppressWarnings("try")` with a short reason.
Other compiler diagnostics remain enabled.

The Make targets run Maven through `bin/check-maven-warnings.sh`. It streams the
complete build output, preserves command failures, and rejects Maven warning
messages from packaging and other plugins. Local checks and CI use these same
targets. Direct Maven invocations also reject compiler warnings through the
parent POM's `failOnWarning` setting.

Executable JARs merge dependency `META-INF/LICENSE` and `META-INF/NOTICE` text,
retain service registrations, and generate their own executable manifests.
Dependency module descriptors and signatures are excluded from these combined
archives. Other overlapping classes or resources still produce warnings and fail
the checked Make targets. Library and source JARs keep their normal packaging.

## Legacy cross-process harness

The original interoperability adapter remains available for broader storage,
Expand Down
10 changes: 9 additions & 1 deletion java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@
<build><plugins>
<!-- Shade replaces the thin adapter jar; always recreate it before adding dependencies. -->
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.5.1</version><configuration><forceCreation>true</forceCreation></configuration></plugin>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.14.1</version><configuration><parameters>true</parameters><showWarnings>true</showWarnings><compilerArgs><arg>-Xlint:all</arg></compilerArgs></configuration></plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.14.1</version>
<configuration>
<parameters>true</parameters>
<showWarnings>true</showWarnings>
<failOnWarning>true</failOnWarning>
<compilerArgs><arg>-Xlint:all</arg></compilerArgs>
</configuration>
</plugin>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.5.4</version><configuration><argLine>--enable-native-access=ALL-UNNAMED</argLine></configuration></plugin>
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-source-plugin</artifactId><version>3.3.1</version><executions><execution><goals><goal>jar-no-fork</goal></goals></execution></executions></plugin>
<plugin><groupId>com.diffplug.spotless</groupId><artifactId>spotless-maven-plugin</artifactId><version>2.44.3</version><configuration><java><googleJavaFormat><version>1.28.0</version></googleJavaFormat></java></configuration><executions><execution><phase>verify</phase><goals><goal>check</goal></goals></execution></executions></plugin>
Expand Down
5 changes: 5 additions & 0 deletions java/river/src/test/java/com/riverqueue/ClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ public void insertBegin(
}

@Test
@SuppressWarnings("try") // Resources intentionally scope background activity.
void fetchOnlyKnownKindsLeavesPeerJobsAvailable() throws Exception {
var river = database();
var peer = JobType.of("peer_only", Email.class);
Expand Down Expand Up @@ -523,6 +524,7 @@ public void insertBegin(
}

@Test
@SuppressWarnings("try") // Resources intentionally scope background activity.
void sqliteQueueNotificationFailureRollsBackQueueMutation() throws Exception {
var river =
new Client(
Expand Down Expand Up @@ -555,6 +557,7 @@ void sqliteQueueNotificationFailureRollsBackQueueMutation() throws Exception {
}

@Test
@SuppressWarnings("try") // Resources intentionally scope background activity.
void committedCompletionWinsOverSubsequentHandlerFailure() throws Exception {
var river = database();
var event = new CompletableFuture<Workers.Event>();
Expand Down Expand Up @@ -585,6 +588,7 @@ void committedCompletionWinsOverSubsequentHandlerFailure() throws Exception {
}

@Test
@SuppressWarnings("try") // Resources intentionally scope background activity.
void retryPolicyReceivesFailedAttemptSnapshot() throws Exception {
var river = database();
var observed = new CompletableFuture<Job<?>>();
Expand Down Expand Up @@ -623,6 +627,7 @@ void retryPolicyReceivesFailedAttemptSnapshot() throws Exception {
}

@Test
@SuppressWarnings("try") // Resources intentionally scope background activity.
void jobKindLengthMatchesDatabaseConstraint() throws Exception {
var client = database();
var type = JobType.of("k".repeat(127), Email.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ void registrationRejectsNullEntries() {
}

@Test
@SuppressWarnings("try") // Resources intentionally scope background activity.
void workBeginFailureSkipsHandlerAndEndHooks() throws Exception {
var trace = new ArrayList<String>();
var river =
Expand Down Expand Up @@ -222,6 +223,7 @@ void workBeginFailureSkipsHandlerAndEndHooks() throws Exception {
"failure,replace",
"success,throw"
})
@SuppressWarnings("try") // Resources intentionally scope background activity.
void workEndHooksChainOutcomesInsideMiddleware(String outcome, String action) throws Exception {
var trace = new ArrayList<String>();
var failure = new IOException("handler failed");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ void deletesOldestRowsInBoundedBatchesAndRetainsCutoff() {

@ParameterizedTest
@ValueSource(booleans = {false, true})
@SuppressWarnings("try") // Resources intentionally scope background activity.
void leaderCleansNotificationsEvenWhenPluginOwnsJobCleaning(boolean pluginOwnsCleaning)
throws Exception {
var database = TestDatabase.sqlite(directory.resolve("river.db"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class NotificationTest {
@TempDir Path directory;

@Test
@SuppressWarnings("try") // Resources intentionally scope background activity.
void sqliteListenerRecoversFromAnInitialDatabaseFailure() throws Exception {
var database = TestDatabase.sqlite(directory.resolve("river.db"));
var fail = new AtomicBoolean(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ else if (cause == WorkContext.Cancellation.SHUTDOWN) {

@ParameterizedTest
@ValueSource(strings = {"decode", "work"})
@SuppressWarnings("try") // Resources intentionally scope background activity.
void peerTimeoutInterruptsTheOwningWorker(String phase) throws Exception {
var entered = new CompletableFuture<Void>();
var interrupted = new CompletableFuture<Void>();
Expand Down
1 change: 1 addition & 0 deletions java/river/src/test/java/com/riverqueue/PluginTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ void pluginRegistrationRetainsDuplicates() {

@ParameterizedTest
@ValueSource(booleans = {false, true})
@SuppressWarnings("try") // Resources intentionally scope background activity.
void pluginsActivateAllJobInterfacesBeforeExplicitRegistrations(boolean pluginsFirst)
throws Exception {
var trace = new CopyOnWriteArrayList<String>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ private Client client() {
}

@Test
@SuppressWarnings("try") // Resources intentionally scope background activity.
void periodicMetadata() throws Exception {
var keys = Conformance.fixture("protocol_values.json").required("metadata_keys");
var client = client();
Expand Down Expand Up @@ -59,6 +60,7 @@ void periodicMetadata() throws Exception {
}

@Test
@SuppressWarnings("try") // Resources intentionally scope background activity.
void rescueCountAndOutput() throws Exception {
var fixture = Conformance.fixture("protocol_values.json");
var keys = fixture.required("metadata_keys");
Expand Down
3 changes: 3 additions & 0 deletions java/river/src/test/java/com/riverqueue/RescueTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class RescueTest {

@ParameterizedTest
@ValueSource(strings = {"default", "disabled", "error", "override", "throw", "zero"})
@SuppressWarnings("try") // Resources intentionally scope background activity.
void pilotReceivesWorkerTimeoutCallback(String mode) throws Exception {
var request = new CompletableFuture<Pilot.Rescue>();
var errors = new LinkedBlockingQueue<Throwable>();
Expand Down Expand Up @@ -198,6 +199,7 @@ public void work(WorkContext<String> context) {

@ParameterizedTest
@ValueSource(booleans = {false, true})
@SuppressWarnings("try") // Resources intentionally scope background activity.
void rescueHonorsWorkerTimeouts(boolean defaultDisabled) throws Exception {
var maintained = new CompletableFuture<Void>();
var errors = new LinkedBlockingQueue<Throwable>();
Expand Down Expand Up @@ -293,6 +295,7 @@ public void work(WorkContext<String> context) {

@ParameterizedTest
@ValueSource(strings = {"local", "peer", "peer_claim_retry", "peer_notification_retry"})
@SuppressWarnings("try") // Resources intentionally scope background activity.
void shortRescuedRetriesWakeFetchingBeforeMaintenance(String mode) throws Exception {
boolean peer = !mode.equals("local");
var database = databases.open(directory.resolve("river.db"));
Expand Down
Loading
Loading