Skip to content
Open
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
2 changes: 1 addition & 1 deletion .fernignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Custom client wrappers (extend generated DeepgramApiClient with Bearer auth, session ID)
# Custom client wrappers (Bearer auth, session ID, and owned HTTP-resource lifecycle)
# Flat paths (local generation strips package-prefix)
src/main/java/DeepgramClient.java
src/main/java/AsyncDeepgramClient.java
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ How to identify:

Current permanently frozen files:

- `src/main/java/com/deepgram/DeepgramClient.java`, `src/main/java/com/deepgram/AsyncDeepgramClient.java`, `src/main/java/com/deepgram/DeepgramClientBuilder.java`, `src/main/java/com/deepgram/AsyncDeepgramClientBuilder.java` - custom wrapper entrypoints that add Bearer auth, session ID support, and custom transport behavior on top of Fern's generated API client
- `src/main/java/com/deepgram/DeepgramClient.java`, `src/main/java/com/deepgram/AsyncDeepgramClient.java`, `src/main/java/com/deepgram/DeepgramClientBuilder.java`, `src/main/java/com/deepgram/AsyncDeepgramClientBuilder.java` - custom wrapper entrypoints that add Bearer auth, session ID support, custom transport behavior, and ownership-aware HTTP-resource lifecycle management on top of Fern's generated API client
- `src/main/java/com/deepgram/core/transport/` - hand-written transport abstraction
- `build.gradle`, `settings.gradle`, `gradle/`, `gradlew`, `gradlew.bat`, `pom.xml`, `Makefile` - build and project configuration
- `README.md`, `CHANGELOG.md`, `CONTRIBUTING.md`, `LICENSE`, `docs/` - docs
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,21 @@ DeepgramClient client = DeepgramClient.builder()
.build();
```

### Resource lifecycle

Close an SDK-created client when your application is finished with it. This releases the OkHttp
dispatcher and connection pool, which is especially important for short-lived command-line programs
that use WebSockets.

```java
try (DeepgramClient client = DeepgramClient.builder().build()) {
// Use the client.
}
```

If you provide an `OkHttpClient` through `.httpClient(...)`, you retain ownership and must close its
resources yourself.

## Features

### Speech-to-Text (Listen)
Expand Down
22 changes: 21 additions & 1 deletion src/main/java/com/deepgram/AsyncDeepgramClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,32 @@
*/
import com.deepgram.core.ClientOptions;

public class AsyncDeepgramClient extends AsyncDeepgramApiClient {
public class AsyncDeepgramClient extends AsyncDeepgramApiClient implements AutoCloseable {
private final boolean ownsHttpClient;

public AsyncDeepgramClient(ClientOptions clientOptions) {
this(clientOptions, false);
}

AsyncDeepgramClient(ClientOptions clientOptions, boolean ownsHttpClient) {
super(clientOptions);
this.ownsHttpClient = ownsHttpClient;
}

public static AsyncDeepgramClientBuilder builder() {
return new AsyncDeepgramClientBuilder();
}

/**
* Releases resources owned by an SDK-created HTTP client. Clients supplied through
* {@link AsyncDeepgramClientBuilder#httpClient(okhttp3.OkHttpClient)} remain owned by the caller.
*/
@Override
public void close() {
if (!ownsHttpClient) {
return;
}
clientOptions.httpClient().dispatcher().executorService().shutdown();
clientOptions.httpClient().connectionPool().evictAll();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public class AsyncDeepgramClientBuilder extends AsyncDeepgramApiClientBuilder {

private DeepgramTransportFactory transportFactory;

private boolean hasCustomHttpClient;

/**
* Sets a custom transport factory for all WebSocket connections. When set, WebSocket clients will use this factory
* instead of the default OkHttp WebSocket. Use this to route Deepgram API calls through alternative transports such
Expand Down Expand Up @@ -87,6 +89,7 @@ public AsyncDeepgramClientBuilder maxRetries(int maxRetries) {

@Override
public AsyncDeepgramClientBuilder httpClient(OkHttpClient httpClient) {
this.hasCustomHttpClient = httpClient != null;
super.httpClient(httpClient);
return this;
}
Expand Down Expand Up @@ -149,6 +152,6 @@ public AsyncDeepgramClient build() {
"Please provide apiKey, accessToken, or set the DEEPGRAM_API_KEY environment variable.");
}
validateConfiguration();
return new AsyncDeepgramClient(buildClientOptions());
return new AsyncDeepgramClient(buildClientOptions(), !hasCustomHttpClient);
}
}
22 changes: 21 additions & 1 deletion src/main/java/com/deepgram/DeepgramClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,32 @@
*/
import com.deepgram.core.ClientOptions;

public class DeepgramClient extends DeepgramApiClient {
public class DeepgramClient extends DeepgramApiClient implements AutoCloseable {
private final boolean ownsHttpClient;

public DeepgramClient(ClientOptions clientOptions) {
this(clientOptions, false);
}

DeepgramClient(ClientOptions clientOptions, boolean ownsHttpClient) {
super(clientOptions);
this.ownsHttpClient = ownsHttpClient;
}

public static DeepgramClientBuilder builder() {
return new DeepgramClientBuilder();
}

/**
* Releases resources owned by an SDK-created HTTP client. Clients supplied through
* {@link DeepgramClientBuilder#httpClient(okhttp3.OkHttpClient)} remain owned by the caller.
*/
@Override
public void close() {
if (!ownsHttpClient) {
return;
}
clientOptions.httpClient().dispatcher().executorService().shutdown();
clientOptions.httpClient().connectionPool().evictAll();
}
}
5 changes: 4 additions & 1 deletion src/main/java/com/deepgram/DeepgramClientBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public class DeepgramClientBuilder extends DeepgramApiClientBuilder {

private DeepgramTransportFactory transportFactory;

private boolean hasCustomHttpClient;

/**
* Sets a custom transport factory for all WebSocket connections. When set, WebSocket clients will use this factory
* instead of the default OkHttp WebSocket. Use this to route Deepgram API calls through alternative transports such
Expand Down Expand Up @@ -86,6 +88,7 @@ public DeepgramClientBuilder maxRetries(int maxRetries) {

@Override
public DeepgramClientBuilder httpClient(OkHttpClient httpClient) {
this.hasCustomHttpClient = httpClient != null;
super.httpClient(httpClient);
return this;
}
Expand Down Expand Up @@ -148,6 +151,6 @@ public DeepgramClient build() {
"Please provide apiKey, accessToken, or set the DEEPGRAM_API_KEY environment variable.");
}
validateConfiguration();
return new DeepgramClient(buildClientOptions());
return new DeepgramClient(buildClientOptions(), !hasCustomHttpClient);
}
}
75 changes: 75 additions & 0 deletions src/test/java/com/deepgram/ClientBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,81 @@ void testCustomHttpClient() {
}
}

@Nested
@DisplayName("Client lifecycle")
class ClientLifecycle {
@Test
@DisplayName("closing the default client releases SDK-owned HTTP resources")
void closesDefaultClientResources() {
DeepgramClient client = DeepgramClient.builder().apiKey("test-key").build();

client.close();

assertThat(client.clientOptions
.httpClient()
.dispatcher()
.executorService()
.isShutdown())
.isTrue();
}

@Test
@DisplayName("closing the default async client releases SDK-owned HTTP resources")
void closesDefaultAsyncClientResources() {
AsyncDeepgramClient client =
AsyncDeepgramClient.builder().apiKey("test-key").build();

client.close();

assertThat(client.clientOptions
.httpClient()
.dispatcher()
.executorService()
.isShutdown())
.isTrue();
}

@Test
@DisplayName("closing a client does not release caller-owned HTTP resources")
void doesNotCloseCustomClientResources() {
OkHttpClient customHttpClient = new OkHttpClient.Builder().build();
DeepgramClient client = DeepgramClient.builder()
.apiKey("test-key")
.httpClient(customHttpClient)
.build();

try {
client.close();

assertThat(customHttpClient.dispatcher().executorService().isShutdown())
.isFalse();
} finally {
customHttpClient.dispatcher().executorService().shutdown();
customHttpClient.connectionPool().evictAll();
}
}

@Test
@DisplayName("closing an async client does not release caller-owned HTTP resources")
void doesNotCloseCustomAsyncClientResources() {
OkHttpClient customHttpClient = new OkHttpClient.Builder().build();
AsyncDeepgramClient client = AsyncDeepgramClient.builder()
.apiKey("test-key")
.httpClient(customHttpClient)
.build();

try {
client.close();

assertThat(customHttpClient.dispatcher().executorService().isShutdown())
.isFalse();
} finally {
customHttpClient.dispatcher().executorService().shutdown();
customHttpClient.connectionPool().evictAll();
}
}
}

@Nested
@DisplayName("Custom headers configuration")
class CustomHeadersConfiguration {
Expand Down
Loading