diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index efc696cf..e49be765 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "7.5.1"
+ ".": "7.5.2"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12627a09..c5503aa0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Changelog
+## 7.5.2 (2025-08-20)
+
+Full Changelog: [v7.5.1...v7.5.2](https://github.com/Finch-API/finch-api-java/compare/v7.5.1...v7.5.2)
+
+### Chores
+
+* **client:** refactor closing / shutdown ([133f431](https://github.com/Finch-API/finch-api-java/commit/133f4314793c2b74f97970d257b1c9d25c9ed3de))
+* **internal:** support running formatters directly ([fb89e60](https://github.com/Finch-API/finch-api-java/commit/fb89e608a0822ba5122f9ea5e3fa0fa561b1f388))
+
## 7.5.1 (2025-08-14)
Full Changelog: [v7.5.0...v7.5.1](https://github.com/Finch-API/finch-api-java/compare/v7.5.0...v7.5.1)
diff --git a/README.md b/README.md
index de50ffbe..99f7362a 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
-[](https://central.sonatype.com/artifact/com.tryfinch.api/finch-java/7.5.1)
-[](https://javadoc.io/doc/com.tryfinch.api/finch-java/7.5.1)
+[](https://central.sonatype.com/artifact/com.tryfinch.api/finch-java/7.5.2)
+[](https://javadoc.io/doc/com.tryfinch.api/finch-java/7.5.2)
@@ -15,7 +15,7 @@ It is generated with [Stainless](https://www.stainless.com/).
-The REST API documentation can be found on [developer.tryfinch.com](https://developer.tryfinch.com/). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.tryfinch.api/finch-java/7.5.1).
+The REST API documentation can be found on [developer.tryfinch.com](https://developer.tryfinch.com/). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.tryfinch.api/finch-java/7.5.2).
@@ -26,7 +26,7 @@ The REST API documentation can be found on [developer.tryfinch.com](https://deve
### Gradle
```kotlin
-implementation("com.tryfinch.api:finch-java:7.5.1")
+implementation("com.tryfinch.api:finch-java:7.5.2")
```
### Maven
@@ -35,7 +35,7 @@ implementation("com.tryfinch.api:finch-java:7.5.1")
com.tryfinch.api
finch-java
- 7.5.1
+ 7.5.2
```
diff --git a/build.gradle.kts b/build.gradle.kts
index 41d4588b..7c4ec2b8 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,7 +8,7 @@ repositories {
allprojects {
group = "com.tryfinch.api"
- version = "7.5.1" // x-release-please-version
+ version = "7.5.2" // x-release-please-version
}
subprojects {
diff --git a/finch-java-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClient.kt b/finch-java-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClient.kt
index a41f3cdd..68859894 100644
--- a/finch-java-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClient.kt
+++ b/finch-java-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClient.kt
@@ -126,6 +126,8 @@ class FinchOkHttpClient private constructor() {
* The executor to use for running [AsyncStreamResponse.Handler] callbacks.
*
* Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
*/
fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
clientOptions.streamHandlerExecutor(streamHandlerExecutor)
diff --git a/finch-java-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClientAsync.kt b/finch-java-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClientAsync.kt
index 23d05eb1..668500aa 100644
--- a/finch-java-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClientAsync.kt
+++ b/finch-java-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClientAsync.kt
@@ -126,6 +126,8 @@ class FinchOkHttpClientAsync private constructor() {
* The executor to use for running [AsyncStreamResponse.Handler] callbacks.
*
* Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
*/
fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
clientOptions.streamHandlerExecutor(streamHandlerExecutor)
diff --git a/finch-java-core/src/main/kotlin/com/tryfinch/api/client/FinchClientAsyncImpl.kt b/finch-java-core/src/main/kotlin/com/tryfinch/api/client/FinchClientAsyncImpl.kt
index a26bbdbb..fadbac2a 100644
--- a/finch-java-core/src/main/kotlin/com/tryfinch/api/client/FinchClientAsyncImpl.kt
+++ b/finch-java-core/src/main/kotlin/com/tryfinch/api/client/FinchClientAsyncImpl.kt
@@ -200,7 +200,7 @@ class FinchClientAsyncImpl(private val clientOptions: ClientOptions) : FinchClie
@get:JsonProperty("provider_id") val providerId: String,
)
- override fun close() = clientOptions.httpClient.close()
+ override fun close() = clientOptions.close()
class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) :
FinchClientAsync.WithRawResponse {
diff --git a/finch-java-core/src/main/kotlin/com/tryfinch/api/client/FinchClientImpl.kt b/finch-java-core/src/main/kotlin/com/tryfinch/api/client/FinchClientImpl.kt
index b0d6cd0e..970bf6fc 100644
--- a/finch-java-core/src/main/kotlin/com/tryfinch/api/client/FinchClientImpl.kt
+++ b/finch-java-core/src/main/kotlin/com/tryfinch/api/client/FinchClientImpl.kt
@@ -189,7 +189,7 @@ class FinchClientImpl(private val clientOptions: ClientOptions) : FinchClient {
@get:JsonProperty("provider_id") val providerId: String,
)
- override fun close() = clientOptions.httpClient.close()
+ override fun close() = clientOptions.close()
class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) :
FinchClient.WithRawResponse {
diff --git a/finch-java-core/src/main/kotlin/com/tryfinch/api/core/ClientOptions.kt b/finch-java-core/src/main/kotlin/com/tryfinch/api/core/ClientOptions.kt
index 6e4b2947..42a6c1e2 100644
--- a/finch-java-core/src/main/kotlin/com/tryfinch/api/core/ClientOptions.kt
+++ b/finch-java-core/src/main/kotlin/com/tryfinch/api/core/ClientOptions.kt
@@ -14,6 +14,7 @@ import java.time.Duration
import java.util.Base64
import java.util.Optional
import java.util.concurrent.Executor
+import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import java.util.concurrent.atomic.AtomicLong
@@ -27,6 +28,8 @@ private constructor(
* The HTTP client to use in the SDK.
*
* Use the one published in `finch-java-client-okhttp` or implement your own.
+ *
+ * This class takes ownership of the client and closes it when closed.
*/
@get:JvmName("httpClient") val httpClient: HttpClient,
/**
@@ -48,6 +51,8 @@ private constructor(
* The executor to use for running [AsyncStreamResponse.Handler] callbacks.
*
* Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
*/
@get:JvmName("streamHandlerExecutor") val streamHandlerExecutor: Executor,
/**
@@ -186,6 +191,8 @@ private constructor(
* The HTTP client to use in the SDK.
*
* Use the one published in `finch-java-client-okhttp` or implement your own.
+ *
+ * This class takes ownership of the client and closes it when closed.
*/
fun httpClient(httpClient: HttpClient) = apply {
this.httpClient = PhantomReachableClosingHttpClient(httpClient)
@@ -214,9 +221,14 @@ private constructor(
* The executor to use for running [AsyncStreamResponse.Handler] callbacks.
*
* Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
*/
fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
- this.streamHandlerExecutor = streamHandlerExecutor
+ this.streamHandlerExecutor =
+ if (streamHandlerExecutor is ExecutorService)
+ PhantomReachableExecutorService(streamHandlerExecutor)
+ else streamHandlerExecutor
}
/**
@@ -493,4 +505,19 @@ private constructor(
)
}
}
+
+ /**
+ * Closes these client options, relinquishing any underlying resources.
+ *
+ * This is purposefully not inherited from [AutoCloseable] because the client options are
+ * long-lived and usually should not be synchronously closed via try-with-resources.
+ *
+ * It's also usually not necessary to call this method at all. the default client automatically
+ * releases threads and connections if they remain idle, but if you are writing an application
+ * that needs to aggressively release unused resources, then you may call this method.
+ */
+ fun close() {
+ httpClient.close()
+ (streamHandlerExecutor as? ExecutorService)?.shutdown()
+ }
}
diff --git a/finch-java-core/src/main/kotlin/com/tryfinch/api/core/PhantomReachableExecutorService.kt b/finch-java-core/src/main/kotlin/com/tryfinch/api/core/PhantomReachableExecutorService.kt
new file mode 100644
index 00000000..2a8fbf64
--- /dev/null
+++ b/finch-java-core/src/main/kotlin/com/tryfinch/api/core/PhantomReachableExecutorService.kt
@@ -0,0 +1,58 @@
+package com.tryfinch.api.core
+
+import java.util.concurrent.Callable
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Future
+import java.util.concurrent.TimeUnit
+
+/**
+ * A delegating wrapper around an [ExecutorService] that shuts it down once it's only phantom
+ * reachable.
+ *
+ * This class ensures the [ExecutorService] is shut down even if the user forgets to do it.
+ */
+internal class PhantomReachableExecutorService(private val executorService: ExecutorService) :
+ ExecutorService {
+ init {
+ closeWhenPhantomReachable(this) { executorService.shutdown() }
+ }
+
+ override fun execute(command: Runnable) = executorService.execute(command)
+
+ override fun shutdown() = executorService.shutdown()
+
+ override fun shutdownNow(): MutableList = executorService.shutdownNow()
+
+ override fun isShutdown(): Boolean = executorService.isShutdown
+
+ override fun isTerminated(): Boolean = executorService.isTerminated
+
+ override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean =
+ executorService.awaitTermination(timeout, unit)
+
+ override fun submit(task: Callable): Future = executorService.submit(task)
+
+ override fun submit(task: Runnable, result: T): Future =
+ executorService.submit(task, result)
+
+ override fun submit(task: Runnable): Future<*> = executorService.submit(task)
+
+ override fun invokeAll(
+ tasks: MutableCollection>
+ ): MutableList> = executorService.invokeAll(tasks)
+
+ override fun invokeAll(
+ tasks: MutableCollection>,
+ timeout: Long,
+ unit: TimeUnit,
+ ): MutableList> = executorService.invokeAll(tasks, timeout, unit)
+
+ override fun invokeAny(tasks: MutableCollection>): T =
+ executorService.invokeAny(tasks)
+
+ override fun invokeAny(
+ tasks: MutableCollection>,
+ timeout: Long,
+ unit: TimeUnit,
+ ): T = executorService.invokeAny(tasks, timeout, unit)
+}
diff --git a/scripts/format b/scripts/format
index 7c0be4d5..65db1769 100755
--- a/scripts/format
+++ b/scripts/format
@@ -4,5 +4,18 @@ set -e
cd "$(dirname "$0")/.."
-echo "==> Running formatters"
-./gradlew format
+if command -v ktfmt &> /dev/null; then
+ echo "==> Running ktfmt"
+ ./scripts/kotlin-format
+else
+ echo "==> Running gradlew formatKotlin"
+ ./gradlew formatKotlin
+fi
+
+if command -v palantir-java-format &> /dev/null; then
+ echo "==> Running palantir-java-format"
+ ./scripts/java-format
+else
+ echo "==> Running gradlew formatJava"
+ ./gradlew formatJava
+fi
diff --git a/scripts/java-format b/scripts/java-format
new file mode 100755
index 00000000..ad5febce
--- /dev/null
+++ b/scripts/java-format
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+find . -name "*.java" -not -path "./buildSrc/build/*" -print0 | xargs -0 -r palantir-java-format --palantir --replace "$@"
diff --git a/scripts/kotlin-format b/scripts/kotlin-format
new file mode 100755
index 00000000..3b8be9ea
--- /dev/null
+++ b/scripts/kotlin-format
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+find . -name "*.kt" -not -path "./buildSrc/build/*" -print0 | xargs -0 -r ktfmt --kotlinlang-style "$@"
diff --git a/scripts/lint b/scripts/lint
index aea8af71..dbc8f776 100755
--- a/scripts/lint
+++ b/scripts/lint
@@ -5,4 +5,19 @@ set -e
cd "$(dirname "$0")/.."
echo "==> Running lints"
-./gradlew lint
+
+if command -v ktfmt &> /dev/null; then
+ echo "==> Checking ktfmt"
+ ./scripts/kotlin-format --dry-run --set-exit-if-changed
+else
+ echo "==> Running gradlew lintKotlin"
+ ./gradlew lintKotlin
+fi
+
+if command -v palantir-java-format &> /dev/null; then
+ echo "==> Checking palantir-java-format"
+ ./scripts/java-format --dry-run --set-exit-if-changed
+else
+ echo "==> Running gradlew lintJava"
+ ./gradlew lintJava
+fi