Skip to content

Commit a822436

Browse files
authored
Merge pull request firecrawl#3463 from firecrawl/devin/1777532789-fix-java-sdk-qa-issues
fix(java-sdk): reject explicitly empty API keys and add missing async lifecycle methods
2 parents 9d0e1e2 + 1157d03 commit a822436

3 files changed

Lines changed: 124 additions & 1 deletion

File tree

apps/java-sdk/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
}
55

66
group = "com.firecrawl"
7-
version = "1.3.0"
7+
version = "1.4.0"
88

99
java {
1010
sourceCompatibility = JavaVersion.VERSION_11

apps/java-sdk/src/main/java/com/firecrawl/client/FirecrawlClient.java

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,116 @@ public CompletableFuture<BrowserListResponse> listBrowsersAsync(String status) {
902902
return CompletableFuture.supplyAsync(() -> listBrowsers(status), asyncExecutor);
903903
}
904904

905+
/**
906+
* Asynchronously starts a crawl job and returns immediately with the job reference.
907+
*
908+
* @param url the URL to start crawling from
909+
* @param options crawl configuration options
910+
* @return a CompletableFuture that resolves to the CrawlResponse
911+
*/
912+
public CompletableFuture<CrawlResponse> startCrawlAsync(String url, CrawlOptions options) {
913+
return CompletableFuture.supplyAsync(() -> startCrawl(url, options), asyncExecutor);
914+
}
915+
916+
/**
917+
* Asynchronously gets the status of a crawl job.
918+
*
919+
* @param jobId the crawl job ID
920+
* @return a CompletableFuture that resolves to the CrawlJob
921+
*/
922+
public CompletableFuture<CrawlJob> getCrawlStatusAsync(String jobId) {
923+
return CompletableFuture.supplyAsync(() -> getCrawlStatus(jobId), asyncExecutor);
924+
}
925+
926+
/**
927+
* Asynchronously cancels a crawl job.
928+
*
929+
* @param jobId the crawl job ID
930+
* @return a CompletableFuture that resolves to the cancellation response
931+
*/
932+
public CompletableFuture<Map<String, Object>> cancelCrawlAsync(String jobId) {
933+
return CompletableFuture.supplyAsync(() -> cancelCrawl(jobId), asyncExecutor);
934+
}
935+
936+
/**
937+
* Asynchronously starts a batch scrape job and returns immediately with the job reference.
938+
*
939+
* @param urls the URLs to scrape
940+
* @param options batch scrape configuration options
941+
* @return a CompletableFuture that resolves to the BatchScrapeResponse
942+
*/
943+
public CompletableFuture<BatchScrapeResponse> startBatchScrapeAsync(List<String> urls, BatchScrapeOptions options) {
944+
return CompletableFuture.supplyAsync(() -> startBatchScrape(urls, options), asyncExecutor);
945+
}
946+
947+
/**
948+
* Asynchronously gets the status of a batch scrape job.
949+
*
950+
* @param jobId the batch scrape job ID
951+
* @return a CompletableFuture that resolves to the BatchScrapeJob
952+
*/
953+
public CompletableFuture<BatchScrapeJob> getBatchScrapeStatusAsync(String jobId) {
954+
return CompletableFuture.supplyAsync(() -> getBatchScrapeStatus(jobId), asyncExecutor);
955+
}
956+
957+
/**
958+
* Asynchronously cancels a batch scrape job.
959+
*
960+
* @param jobId the batch scrape job ID
961+
* @return a CompletableFuture that resolves to the cancellation response
962+
*/
963+
public CompletableFuture<Map<String, Object>> cancelBatchScrapeAsync(String jobId) {
964+
return CompletableFuture.supplyAsync(() -> cancelBatchScrape(jobId), asyncExecutor);
965+
}
966+
967+
/**
968+
* Asynchronously starts an agent task and returns immediately with the job reference.
969+
*
970+
* @param options agent configuration options
971+
* @return a CompletableFuture that resolves to the AgentResponse
972+
*/
973+
public CompletableFuture<AgentResponse> startAgentAsync(AgentOptions options) {
974+
return CompletableFuture.supplyAsync(() -> startAgent(options), asyncExecutor);
975+
}
976+
977+
/**
978+
* Asynchronously gets the status of an agent task.
979+
*
980+
* @param jobId the agent job ID
981+
* @return a CompletableFuture that resolves to the AgentStatusResponse
982+
*/
983+
public CompletableFuture<AgentStatusResponse> getAgentStatusAsync(String jobId) {
984+
return CompletableFuture.supplyAsync(() -> getAgentStatus(jobId), asyncExecutor);
985+
}
986+
987+
/**
988+
* Asynchronously cancels an agent task.
989+
*
990+
* @param jobId the agent job ID
991+
* @return a CompletableFuture that resolves to the cancellation response
992+
*/
993+
public CompletableFuture<Map<String, Object>> cancelAgentAsync(String jobId) {
994+
return CompletableFuture.supplyAsync(() -> cancelAgent(jobId), asyncExecutor);
995+
}
996+
997+
/**
998+
* Asynchronously gets concurrency info.
999+
*
1000+
* @return a CompletableFuture that resolves to the ConcurrencyCheck
1001+
*/
1002+
public CompletableFuture<ConcurrencyCheck> getConcurrencyAsync() {
1003+
return CompletableFuture.supplyAsync(this::getConcurrency, asyncExecutor);
1004+
}
1005+
1006+
/**
1007+
* Asynchronously gets credit usage.
1008+
*
1009+
* @return a CompletableFuture that resolves to the CreditUsage
1010+
*/
1011+
public CompletableFuture<CreditUsage> getCreditUsageAsync() {
1012+
return CompletableFuture.supplyAsync(this::getCreditUsage, asyncExecutor);
1013+
}
1014+
9051015
// ================================================================
9061016
// INTERNAL POLLING HELPERS
9071017
// ================================================================
@@ -1008,6 +1118,7 @@ private void sleep(int seconds) {
10081118
public static final class Builder {
10091119

10101120
private String apiKey;
1121+
private boolean apiKeyExplicitlySet;
10111122
private String apiUrl = DEFAULT_API_URL;
10121123
private long timeoutMs = DEFAULT_TIMEOUT_MS;
10131124
private int maxRetries = DEFAULT_MAX_RETRIES;
@@ -1023,6 +1134,7 @@ private Builder() {}
10231134
*/
10241135
public Builder apiKey(String apiKey) {
10251136
this.apiKey = apiKey;
1137+
this.apiKeyExplicitlySet = true;
10261138
return this;
10271139
}
10281140

@@ -1098,6 +1210,10 @@ public Builder httpClient(OkHttpClient httpClient) {
10981210

10991211
public FirecrawlClient build() {
11001212
String resolvedKey = apiKey;
1213+
if (apiKeyExplicitlySet && (resolvedKey == null || resolvedKey.isBlank())) {
1214+
throw new FirecrawlException(
1215+
"API key cannot be null or empty when explicitly provided.");
1216+
}
11011217
if (resolvedKey == null || resolvedKey.isBlank()) {
11021218
resolvedKey = System.getenv("FIRECRAWL_API_KEY");
11031219
}

apps/java-sdk/src/test/java/com/firecrawl/FirecrawlClientTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ void testBuilderRequiresApiKey() {
2828
);
2929
}
3030

31+
@Test
32+
void testBuilderRejectsExplicitNullApiKey() {
33+
assertThrows(FirecrawlException.class, () ->
34+
FirecrawlClient.builder().apiKey(null).build()
35+
);
36+
}
37+
3138
@Test
3239
void testBuilderAcceptsApiKey() {
3340
// Should not throw — just validates construction

0 commit comments

Comments
 (0)