From 3979862e9a58fe163300a50881b520f212a32139 Mon Sep 17 00:00:00 2001 From: laughingman7743 Date: Mon, 10 Aug 2026 21:58:09 +0900 Subject: [PATCH] fix: avoid NullPointerException in BigQuery.create() when the duplicated job cannot be re-fetched The duplicate-job-id handler dereferences the getJob(...) re-fetch unconditionally, but getJob returns null when the job cannot be seen - most commonly a job outside the US multi-region looked up through a JobId that carries no location. Guard it and fall through to the original Already Exists exception, as the random-id branch below already does. Fixes #14025 --- .../google/cloud/bigquery/BigQueryImpl.java | 26 +++++++++------- .../cloud/bigquery/BigQueryImplTest.java | 31 +++++++++++++++++++ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java index 2ad09c33d7cb..eb835cac4da6 100644 --- a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java +++ b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java @@ -608,18 +608,20 @@ public com.google.api.services.bigquery.model.Job call() throws IOException { // If the Job ALREADY EXISTS, retrieve it. Job job = this.getJob(jobInfo.getJobId(), JobOption.fields(JobField.STATISTICS)); - long jobCreationTime = job.getStatistics().getCreationTime(); - long jobMinStaleTime = System.currentTimeMillis(); - long jobMaxStaleTime = - java.time.Instant.ofEpochMilli(jobMinStaleTime) - .minus(1, java.time.temporal.ChronoUnit.DAYS) - .toEpochMilli(); - - // Only return the job if it has been created in the past 24 hours. - // This is assuming any job older than 24 hours is a valid duplicate JobID - // and not a false positive like b/290419183 - if (jobCreationTime >= jobMaxStaleTime && jobCreationTime <= jobMinStaleTime) { - return job; + if (job != null) { + long jobCreationTime = job.getStatistics().getCreationTime(); + long jobMinStaleTime = System.currentTimeMillis(); + long jobMaxStaleTime = + java.time.Instant.ofEpochMilli(jobMinStaleTime) + .minus(1, java.time.temporal.ChronoUnit.DAYS) + .toEpochMilli(); + + // Only return the job if it has been created in the past 24 hours. + // This is assuming any job older than 24 hours is a valid duplicate JobID + // and not a false positive like b/290419183 + if (jobCreationTime >= jobMaxStaleTime && jobCreationTime <= jobMinStaleTime) { + return job; + } } } } diff --git a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryImplTest.java b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryImplTest.java index 9f2320ab3c35..53ea5491c38a 100644 --- a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryImplTest.java +++ b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryImplTest.java @@ -2144,6 +2144,37 @@ void testCreateJobTryGetNotRandom() throws IOException { any(String.class), eq(id), eq((String) null), eq(withStatisticOption)); } + @Test + void testCreateJobTryGetNotRandomJobNotFound() throws IOException { + Map withStatisticOption = optionMap(JobOption.fields(STATISTICS)); + final String id = "testCreateJobTryGet-id"; + String query = "SELECT * in FOO"; + + when(bigqueryRpcMock.createSkipExceptionTranslation( + jobCapture.capture(), eq(EMPTY_RPC_OPTIONS))) + .thenThrow( + new BigQueryException( + 409, + "already exists, for some reason", + new RuntimeException("Already Exists: Job"))); + when(bigqueryRpcMock.getJobSkipExceptionTranslation( + any(String.class), eq(id), eq((String) null), eq(withStatisticOption))) + .thenThrow(new BigQueryException(404, "Job not found")); + + bigquery = options.getService(); + BigQueryException exception = + Assertions.assertThrows( + BigQueryException.class, + () -> + ((BigQueryImpl) bigquery) + .create(JobInfo.of(JobId.of(id), QueryJobConfiguration.of(query)))); + assertEquals(409, exception.getCode()); + assertEquals("already exists, for some reason", exception.getMessage()); + verify(bigqueryRpcMock) + .getJobSkipExceptionTranslation( + any(String.class), eq(id), eq((String) null), eq(withStatisticOption)); + } + @Test void testCreateJobWithProjectId() throws IOException { JobInfo jobInfo =