> ## AI Agent Context
> Get the complete documentation index at: https://aistudio.google.com/llms.txt
> Use this file to discover all available pages.
> For the full documentation in a single file, fetch: https://aistudio.google.com/llms-full.txt

# Batch API

{% callout type="note" %}
  This feature is currently only available with the generateContent API.
  Please follow the content on this page for more information.

{% /callout %}

The Gemini Batch API is designed to process large volumes of requests
asynchronously at [50% of the standard cost](/docs/pricing).
The target turnaround time is 24 hours, but in majority of cases, it is much
quicker.

Use Batch API for large-scale, non-urgent tasks such as data
pre-processing or running evaluations where an immediate response is not
required.

## Creating a batch job  {% id="create-batch-job" %}

You have two ways to submit your requests in Batch API:

*   **[Inline requests](/docs/batch-api#inline-requests):** A list of
  [`GenerateContentRequest`](https://ai.google.dev/api/batch-mode#GenerateContentRequest) objects
  directly included in your batch creation request. This is suitable for
  smaller batches that keep the total request size under 20MB. The **output**
  returned from the model is a list of `inlineResponse` objects.

*   **[Input file](/docs/batch-api#input-file):** A [JSON Lines (JSONL)](https://jsonlines.org/)
  file where each line contains a complete
  [`GenerateContentRequest`](https://ai.google.dev/api/batch-mode#GenerateContentRequest) object.
  This method is recommended for larger requests. The **output**
  returned from the model is a JSONL file where each line is either a
  `GenerateContentResponse` or a status object.


### Inline requests  {% id="inline-requests" %}

For a small number of requests, you can directly embed the
[`GenerateContentRequest`](https://ai.google.dev/api/batch-mode#GenerateContentRequest) objects
within your [`BatchGenerateContentRequest`](https://ai.google.dev/api/batch-mode#request-body). The
following example calls the
[`BatchGenerateContent`](https://ai.google.dev/api/batch-mode#google.ai.generativelanguage.v1beta.BatchService.BatchGenerateContent)
method with inline requests:

{% tabs %}
  {% tab label="Python" %}
  ```python
  
  from google import genai
  from google.genai import types
  
  client = genai.Client()
  
  # A list of dictionaries, where each is a GenerateContentRequest
  inline_requests = [
      {
          'contents': [{
              'parts': [{'text': 'Tell me a one-sentence joke.'}],
              'role': 'user'
          }]
      },
      {
          'contents': [{
              'parts': [{'text': 'Why is the sky blue?'}],
              'role': 'user'
          }]
      }
  ]
  
  inline_batch_job = client.batches.create(
      model="gemini-3.8-flash",
      src=inline_requests,
      config={
          'display_name': "inlined-requests-job-1",
      },
  )
  
  print(f"Created batch job: {inline_batch_job.name}")
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  
  import {GoogleGenAI} from '@google/genai';
  
  const ai = new GoogleGenAI({});
  
  const inlinedRequests = [
      {
          contents: [{
              parts: [{text: 'Tell me a one-sentence joke.'}],
              role: 'user'
          }]
      },
      {
          contents: [{
              parts: [{'text': 'Why is the sky blue?'}],
              role: 'user'
          }]
      }
  ]
  
  const response = await ai.batches.create({
      model: 'gemini-3.8-flash',
      src: inlinedRequests,
      config: {
          displayName: 'inlined-requests-job-1',
      }
  });
  
  console.log(response);
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import java.util.Arrays;
  import com.google.genai.Client;
  import com.google.genai.types.BatchJob;
  import com.google.genai.types.BatchJobSource;
  import com.google.genai.types.Content;
  import com.google.genai.types.CreateBatchJobConfig;
  import com.google.genai.types.InlinedRequest;
  import com.google.genai.types.Part;
  import java.util.List;
  
  Client client = new Client();
  
  // A list of InlinedRequest objects
  List<InlinedRequest> inlineRequests =
      Arrays.asList(
          InlinedRequest.builder()
              .contents(
                  Arrays.asList(
                      Content.builder()
                          .role("user")
                          .parts(Arrays.asList(Part.fromText("Tell me a one-sentence joke.")))
                          .build()))
              .build(),
          InlinedRequest.builder()
              .contents(
                  Arrays.asList(
                      Content.builder()
                          .role("user")
                          .parts(Arrays.asList(Part.fromText("Why is the sky blue?")))
                          .build()))
              .build());
  
  BatchJobSource src = BatchJobSource.builder().inlinedRequests(inlineRequests).build();
  CreateBatchJobConfig config =
      CreateBatchJobConfig.builder().displayName("inlined-requests-job-1").build();
  
  BatchJob inlineBatchJob = client.batches.create("gemini-3.8-flash", src, config);
  
  System.out.println("Created batch job: " + inlineBatchJob.name().orElse(""));
  ```

  {% /tab %}

  {% tab label="REST" %}
  ```bash
  curl https://generativelanguage.googleapis.com/v1beta/models/gemini-3.8-flash:batchGenerateContent \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -X POST \
  -H "Content-Type:application/json" \
  -d '{
      "batch": {
          "display_name": "my-batch-requests",
          "input_config": {
              "requests": {
                  "requests": [
                      {
                          "request": {"contents": [{"parts": [{"text": "Describe the process of photosynthesis."}]}]},
                          "metadata": {
                              "key": "request-1"
                          }
                      },
                      {
                          "request": {"contents": [{"parts": [{"text": "Describe the process of photosynthesis."}]}]},
                          "metadata": {
                              "key": "request-2"
                          }
                      }
                  ]
              }
          }
      }
  }'
  ```

  {% /tab %}

{% /tabs %}

### Input file  {% id="input-file" %}

For larger sets of requests, prepare a JSON Lines (JSONL) file. Each line in
this file must be a JSON object containing a user-defined key and a request
object, where the request is a valid
[`GenerateContentRequest`](https://ai.google.dev/api/batch-mode#GenerateContentRequest) object. The
user-defined key is used in the response to indicate which output is the result
of which request. For example, the request with the key defined as `request-1`
will have its response annotated with the same key name.

This file is uploaded using the [File API](/docs/files). The maximum
allowed file size for an input file is 2GB.

The following is an example of a JSONL file. You can save it in a file named
`my-batch-requests.json`:

```json
{"key": "request-1", "request": {"contents": [{"parts": [{"text": "Describe the process of photosynthesis."}]}], "generation_config": {"temperature": 0.7}}}
{"key": "request-2", "request": {"contents": [{"parts": [{"text": "What are the main ingredients in a Margherita pizza?"}]}]}}
```

Similarly to inline requests, you can specify other parameters like system
instructions, tools or other configurations in each request JSON.

You can upload this file using the [File API](/docs/files) as
shown in the following example. If
you are working with multimodal input, you can reference other uploaded files
within your JSONL file.

{% tabs %}
  {% tab label="Python" %}
  ```python
  
  import json
  from google import genai
  from google.genai import types
  
  client = genai.Client()
  
  # Create a sample JSONL file
  with open("my-batch-requests.jsonl", "w") as f:
      requests = [
          {"key": "request-1", "request": {"contents": [{"parts": [{"text": "Describe the process of photosynthesis."}]}]}},
          {"key": "request-2", "request": {"contents": [{"parts": [{"text": "What are the main ingredients in a Margherita pizza?"}]}]}}
      ]
      for req in requests:
          f.write(json.dumps(req) + "\n")
  
  # Upload the file to the File API
  uploaded_file = client.files.upload(
      file='my-batch-requests.jsonl',
      config=types.UploadFileConfig(display_name='my-batch-requests', mime_type='jsonl')
  )
  
  print(f"Uploaded file: {uploaded_file.name}")
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  
  import {GoogleGenAI} from '@google/genai';
  import * as fs from "fs";
  import * as path from "path";
  
  const ai = new GoogleGenAI({});
  const fileName = "my-batch-requests.jsonl";
  
  // Define the requests
  const requests = [
      { "key": "request-1", "request": { "contents": [{ "parts": [{ "text": "Describe the process of photosynthesis." }] }] } },
      { "key": "request-2", "request": { "contents": [{ "parts": [{ "text": "What are the main ingredients in a Margherita pizza?" }] }] } }
  ];
  
  // Construct the path to file
  const filePath = fileName;
  
  async function writeBatchRequestsToFile(requests = [{ key: '', request: {} }], filePath = '') {
      try {
          // Use a writable stream for efficiency, especially with larger files.
          const writeStream = fs.createWriteStream(filePath, { flags: 'w' });
  
          writeStream.on('error', (err = null) => {
              console.error(`Error writing to file ${filePath}:`, err);
          });
  
          for (const req of requests) {
              writeStream.write(JSON.stringify(req) + '\n');
          }
  
          writeStream.end();
  
          console.log(`Successfully wrote batch requests to ${filePath}`);
  
      } catch (error) {
          // This catch block is for errors that might occur before stream setup,
          // stream errors are handled by the 'error' event.
          console.error(`An unexpected error occurred:`, error);
      }
  }
  
  // Write to a file.
  await writeBatchRequestsToFile(requests, filePath);
  
  // Upload the file to the File API.
  const uploadedFile = await ai.files.upload({file: 'my-batch-requests.jsonl', config: {
      mimeType: 'jsonl',
  }});
  console.log(uploadedFile.name);
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import java.util.Arrays;
  import com.google.genai.Client;
  import com.google.genai.types.File;
  import com.google.genai.types.UploadFileConfig;
  import java.nio.file.Files;
  import java.nio.file.Paths;
  import java.util.List;
  
  Client client = new Client();
  
  // Create a sample JSONL file
  List<String> requests =
      Arrays.asList(
          "{\"key\": \"request-1\", \"request\": {\"contents\": [{\"parts\": [{\"text\": \"Describe the process of photosynthesis.\"}]}]}}",
          "{\"key\": \"request-2\", \"request\": {\"contents\": [{\"parts\": [{\"text\": \"What are the main ingredients in a Margherita pizza?\"}]}]}}");
  Files.write(Paths.get("my-batch-requests.jsonl"), requests);
  
  // Upload the file to the File API
  UploadFileConfig uploadConfig =
      UploadFileConfig.builder()
          .displayName("my-batch-requests")
          .mimeType("jsonl")
          .build();
  File uploadedFile = client.files.upload("my-batch-requests.jsonl", uploadConfig);
  
  System.out.println("Uploaded file: " + uploadedFile.name().orElse(""));
  ```

  {% /tab %}

  {% tab label="REST" %}
  ```bash
  tmp_batch_input_file=batch_input.tmp
  echo -e '{"contents": [{"parts": [{"text": "Describe the process of photosynthesis."}]}], "generationConfig": {"temperature": 0.7}}\n{"contents": [{"parts": [{"text": "What are the main ingredients in a Margherita pizza?"}]}]}' > batch_input.tmp
  MIME_TYPE=$(file -b --mime-type "${tmp_batch_input_file}")
  NUM_BYTES=$(wc -c < "${tmp_batch_input_file}")
  DISPLAY_NAME=BatchInput
  
  tmp_header_file=upload-header.tmp
  
  # Initial resumable request defining metadata.
  # The upload url is in the response headers dump them to a file.
  curl "https://generativelanguage.googleapis.com/upload/v1beta/files" \
  -D "${tmp_header_file}" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "X-Goog-Upload-Protocol: resumable" \
  -H "X-Goog-Upload-Command: start" \
  -H "X-Goog-Upload-Header-Content-Length: ${NUM_BYTES}" \
  -H "X-Goog-Upload-Header-Content-Type: ${MIME_TYPE}" \
  -H "Content-Type: application/jsonl" \
  -d "{'file': {'display_name': '${DISPLAY_NAME}'}}" 2> /dev/null
  
  upload_url=$(grep -i "x-goog-upload-url: " "${tmp_header_file}" | cut -d" " -f2 | tr -d "\r")
  rm "${tmp_header_file}"
  
  # Upload the actual bytes.
  curl "${upload_url}" \
  -H "Content-Length: ${NUM_BYTES}" \
  -H "X-Goog-Upload-Offset: 0" \
  -H "X-Goog-Upload-Command: upload, finalize" \
  --data-binary "@${tmp_batch_input_file}" 2> /dev/null > file_info.json
  
  file_uri=$(jq ".file.uri" file_info.json)
  ```

  {% /tab %}

{% /tabs %}

The following example calls the
[`BatchGenerateContent`](https://ai.google.dev/api/batch-mode#google.ai.generativelanguage.v1beta.BatchService.BatchGenerateContent)
method with the input file uploaded using File API:

{% tabs %}
  {% tab label="Python" %}
  ```python
  from google import genai
  
  # Assumes `uploaded_file` is the file object from the previous step
  client = genai.Client()
  file_batch_job = client.batches.create(
      model="gemini-3.8-flash",
      src=uploaded_file.name,
      config={
          'display_name': "file-upload-job-1",
      },
  )
  
  print(f"Created batch job: {file_batch_job.name}")
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  // Assumes `uploadedFile` is the file object from the previous step
  const fileBatchJob = await ai.batches.create({
      model: 'gemini-3.8-flash',
      src: uploadedFile.name,
      config: {
          displayName: 'file-upload-job-1',
      }
  });
  
  console.log(fileBatchJob);
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import com.google.genai.Client;
  import com.google.genai.types.BatchJob;
  import com.google.genai.types.BatchJobSource;
  import com.google.genai.types.CreateBatchJobConfig;
  
  Client client = new Client();
  
  // Assumes `uploadedFileName` is the file name from the previous step
  String uploadedFileName = "files/my-batch-requests-id";
  
  BatchJobSource src = BatchJobSource.builder().fileName(uploadedFileName).build();
  CreateBatchJobConfig config =
      CreateBatchJobConfig.builder().displayName("file-upload-job-1").build();
  
  BatchJob fileBatchJob = client.batches.create("gemini-3.8-flash", src, config);
  
  System.out.println("Created batch job: " + fileBatchJob.name().orElse(""));
  ```

  {% /tab %}

  {% tab label="REST" %}
  ```bash
  # Set the File ID taken from the upload response.
  BATCH_INPUT_FILE='files/123456'
  curl https://generativelanguage.googleapis.com/v1beta/models/gemini-3.8-flash:batchGenerateContent \
  -X POST \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type:application/json" \
  -d "{
      'batch': {
          'display_name': 'my-batch-requests',
          'input_config': {
              'file_name': '${BATCH_INPUT_FILE}'
          }
      }
  }"
  ```

  {% /tab %}

{% /tabs %}

When you create a batch job, you will get a job name returned. Use this name
for [monitoring](/docs/batch-api#batch-job-status) the job status as well as
[retrieving the results](/docs/batch-api#retrieve-batch-results) once the job completes.

The following is an example output that contains a job name:

```none

Created batch job from file: batches/123456789
```

### Batch embedding support  {% id="batch-embedding" %}

You can use the Batch API to interact with the
[Embeddings model](/docs/embeddings) for higher throughput.
To create an embeddings batch job with either [inline requests](/docs/batch-api#inline-requests)
or [input files](/docs/batch-api#input-file), use the `batches.create_embeddings` API and
specify the embeddings model.

{% tabs %}
  {% tab label="Python" %}
  ```python
  from google import genai
  
  client = genai.Client()
  
  # Creating an embeddings batch job with an input file request:
  file_job = client.batches.create_embeddings(
      model="gemini-embedding-2",
      src={'file_name': uploaded_batch_requests.name},
      config={'display_name': "Input embeddings batch"},
  )
  
  # Creating an embeddings batch job with an inline request:
  batch_job = client.batches.create_embeddings(
      model="gemini-embedding-2",
      # For a predefined list of requests `inlined_requests`
      src={'inlined_requests': inlined_requests},
      config={'display_name': "Inlined embeddings batch"},
  )
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  // Creating an embeddings batch job with an input file request:
  let fileJob;
  fileJob = await client.batches.createEmbeddings({
      model: 'gemini-embedding-2',
      src: {fileName: uploadedBatchRequests.name},
      config: {displayName: 'Input embeddings batch'},
  });
  console.log(`Created batch job: ${fileJob.name}`);
  
  // Creating an embeddings batch job with an inline request:
  let batchJob;
  batchJob = await client.batches.createEmbeddings({
      model: 'gemini-embedding-2',
      // For a predefined a list of requests `inlinedRequests`
      src: {inlinedRequests: inlinedRequests},
      config: {displayName: 'Inlined embeddings batch'},
  });
  console.log(`Created batch job: ${batchJob.name}`);
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import java.util.Arrays;
  import com.google.genai.Client;
  import com.google.genai.types.BatchJob;
  import com.google.genai.types.Content;
  import com.google.genai.types.CreateEmbeddingsBatchJobConfig;
  import com.google.genai.types.EmbedContentBatch;
  import com.google.genai.types.EmbeddingsBatchJobSource;
  import com.google.genai.types.Part;
  import java.util.List;
  
  Client client = new Client();
  
  String uploadedFileName = "files/my-embedding-requests-id";
  
  // Creating an embeddings batch job with an input file request:
  BatchJob fileJob =
      client.batches.createEmbeddings(
          "gemini-embedding-2",
          EmbeddingsBatchJobSource.builder().fileName(uploadedFileName).build(),
          CreateEmbeddingsBatchJobConfig.builder().displayName("Input embeddings batch").build());
  System.out.println("Created batch job: " + fileJob.name().orElse(""));
  
  // Creating an embeddings batch job with an inline request:
  EmbedContentBatch inlinedRequests =
      EmbedContentBatch.builder()
          .contents(Arrays.asList(Content.fromParts(Part.fromText("What is the meaning of life?"))))
          .build();
  BatchJob batchJob =
      client.batches.createEmbeddings(
          "gemini-embedding-2",
          EmbeddingsBatchJobSource.builder().inlinedRequests(inlinedRequests).build(),
          CreateEmbeddingsBatchJobConfig.builder().displayName("Inlined embeddings batch").build());
  System.out.println("Created batch job: " + batchJob.name().orElse(""));
  ```

  {% /tab %}

{% /tabs %}

Read the Embeddings section in the [Batch API cookbook](https://github.com/google-gemini/cookbook/blob/main/quickstarts/Batch_mode.ipynb)
for more examples.

### Request configuration  {% id="request-config" %}

You can include any request configurations you would use in a standard non-batch
request. For example, you could specify the temperature, system instructions or
even pass in other modalities. The following example shows an example inline
request that contains a system instruction for one of the requests:

{% tabs %}
  {% tab label="Python" %}
  ```python
  inline_requests_list = [
      {'contents': [{'parts': [{'text': 'Write a short poem about a cloud.'}]}]},
      {'contents': [{
          'parts': [{
              'text': 'Write a short poem about a cat.'
              }]
          }],
      'config': {
          'system_instruction': {'parts': [{'text': 'You are a cat. Your name is Neko.'}]}}
      }
  ]
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  inlineRequestsList = [
      {contents: [{parts: [{text: 'Write a short poem about a cloud.'}]}]},
      {contents: [{parts: [{text: 'Write a short poem about a cat.'}]}],
       config: {systemInstruction: {parts: [{text: 'You are a cat. Your name is Neko.'}]}}}
  ]
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import java.util.Arrays;
  import com.google.genai.types.Content;
  import com.google.genai.types.GenerateContentConfig;
  import com.google.genai.types.InlinedRequest;
  import com.google.genai.types.Part;
  import java.util.List;
  
  List<InlinedRequest> inlineRequestsList =
      Arrays.asList(
          InlinedRequest.builder()
              .contents(Arrays.asList(Content.fromParts(Part.fromText("Write a short poem about a cloud."))))
              .build(),
          InlinedRequest.builder()
              .contents(Arrays.asList(Content.fromParts(Part.fromText("Write a short poem about a cat."))))
              .config(
                  GenerateContentConfig.builder()
                      .systemInstruction(
                          Content.fromParts(Part.fromText("You are a cat. Your name is Neko.")))
                      .build())
              .build());
  ```

  {% /tab %}

{% /tabs %}

Similarly can specify tools to use for a request. The following example
shows a request that enables the [Google Search tool](/docs/google-search):

{% tabs %}
  {% tab label="Python" %}
  ```python
  inlined_requests = [
  {'contents': [{'parts': [{'text': 'Who won the euro 1998?'}]}]},
  {'contents': [{'parts': [{'text': 'Who won the euro 2025?'}]}],
   'config':{'tools': [{'google_search': {}}]}}]
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  inlineRequestsList = [
      {contents: [{parts: [{text: 'Who won the euro 1998?'}]}]},
      {contents: [{parts: [{text: 'Who won the euro 2025?'}]}],
       config: {tools: [{googleSearch: {}}]}}
  ]
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import java.util.Arrays;
  import com.google.genai.types.Content;
  import com.google.genai.types.GenerateContentConfig;
  import com.google.genai.types.GoogleSearch;
  import com.google.genai.types.InlinedRequest;
  import com.google.genai.types.Part;
  import com.google.genai.types.Tool;
  import java.util.List;
  
  List<InlinedRequest> inlinedRequests =
      Arrays.asList(
          InlinedRequest.builder()
              .contents(Arrays.asList(Content.fromParts(Part.fromText("Who won the euro 1998?"))))
              .build(),
          InlinedRequest.builder()
              .contents(Arrays.asList(Content.fromParts(Part.fromText("Who won the euro 2025?"))))
              .config(
                  GenerateContentConfig.builder()
                      .tools(Tool.builder().googleSearch(GoogleSearch.builder().build()).build())
                      .build())
              .build());
  ```

  {% /tab %}

{% /tabs %}

You can specify [structured output](/docs/structured-output) as well.
The following example shows how to specify for your batch requests.

{% tabs %}
  {% tab label="Python" %}
  ```python
  import time
  from google import genai
  from pydantic import BaseModel, TypeAdapter
  
  class Recipe(BaseModel):
      recipe_name: str
      ingredients: list[str]
  
  client = genai.Client()
  
  # A list of dictionaries, where each is a GenerateContentRequest
  inline_requests = [
      {
          'contents': [{
              'parts': [{'text': 'List a few popular cookie recipes, and include the amounts of ingredients.'}],
              'role': 'user'
          }],
          'config': {
              'response_mime_type': 'application/json',
              'response_schema': list[Recipe]
          }
      },
      {
          'contents': [{
              'parts': [{'text': 'List a few popular gluten free cookie recipes, and include the amounts of ingredients.'}],
              'role': 'user'
          }],
          'config': {
              'response_mime_type': 'application/json',
              'response_schema': list[Recipe]
          }
      }
  ]
  
  inline_batch_job = client.batches.create(
      model="gemini-3.8-flash",
      src=inline_requests,
      config={
          'display_name': "structured-output-job-1"
      },
  )
  
  # wait for the job to finish
  job_name = inline_batch_job.name
  print(f"Polling status for job: {job_name}")
  
  while True:
      batch_job_inline = client.batches.get(name=job_name)
      if batch_job_inline.state.name in ('JOB_STATE_SUCCEEDED', 'JOB_STATE_FAILED', 'JOB_STATE_CANCELLED', 'JOB_STATE_EXPIRED'):
          break
      print(f"Job not finished. Current state: {batch_job_inline.state.name}. Waiting 30 seconds...")
      time.sleep(30)
  
  print(f"Job finished with state: {batch_job_inline.state.name}")
  
  # print the response
  for i, inline_response in enumerate(batch_job_inline.dest.inlined_responses, start=1):
      print(f"\n--- Response {i} ---")
  
      # Check for a successful response
      if inline_response.response:
          # The .text property is a shortcut to the generated text.
          print(inline_response.response.text)
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  
  import {GoogleGenAI, Type} from '@google/genai';
  
  const ai = new GoogleGenAI({});
  
  const inlinedRequests = [
      {
          contents: [{
              parts: [{text: 'List a few popular cookie recipes, and include the amounts of ingredients.'}],
              role: 'user'
          }],
          config: {
              responseMimeType: 'application/json',
              responseSchema: {
              type: Type.ARRAY,
              items: {
                  type: Type.OBJECT,
                  properties: {
                  'recipeName': {
                      type: Type.STRING,
                      description: 'Name of the recipe',
                      nullable: false,
                  },
                  'ingredients': {
                      type: Type.ARRAY,
                      items: {
                      type: Type.STRING,
                      description: 'Ingredients of the recipe',
                      nullable: false,
                      },
                  },
                  },
                  required: ['recipeName'],
              },
              },
          }
      },
      {
          contents: [{
              parts: [{text: 'List a few popular gluten free cookie recipes, and include the amounts of ingredients.'}],
              role: 'user'
          }],
          config: {
              responseMimeType: 'application/json',
              responseSchema: {
              type: Type.ARRAY,
              items: {
                  type: Type.OBJECT,
                  properties: {
                  'recipeName': {
                      type: Type.STRING,
                      description: 'Name of the recipe',
                      nullable: false,
                  },
                  'ingredients': {
                      type: Type.ARRAY,
                      items: {
                      type: Type.STRING,
                      description: 'Ingredients of the recipe',
                      nullable: false,
                      },
                  },
                  },
                  required: ['recipeName'],
              },
              },
          }
      }
  ]
  
  const inlinedBatchJob = await ai.batches.create({
      model: 'gemini-3.8-flash',
      src: inlinedRequests,
      config: {
          displayName: 'inlined-requests-job-1',
      }
  });
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Arrays;
  import java.util.Collections;
  import com.google.genai.Client;
  import com.google.genai.types.BatchJob;
  import com.google.genai.types.BatchJobSource;
  import com.google.genai.types.Content;
  import com.google.genai.types.CreateBatchJobConfig;
  import com.google.genai.types.GenerateContentConfig;
  import com.google.genai.types.InlinedRequest;
  import com.google.genai.types.InlinedResponse;
  import com.google.genai.types.JobState;
  import com.google.genai.types.Part;
  import com.google.genai.types.Schema;
  import com.google.genai.types.Type;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  Client client = new Client();
  
  Map<String, Schema> properties = new HashMap<>();
  properties.put("recipeName", Schema.builder().type(Type.Known.STRING).build());
  properties.put(
      "ingredients",
      Schema.builder()
          .type(Type.Known.ARRAY)
          .items(Schema.builder().type(Type.Known.STRING).build())
          .build());
  
  Schema recipeSchema =
      Schema.builder()
          .type(Type.Known.ARRAY)
          .items(
              Schema.builder()
                  .type(Type.Known.OBJECT)
                  .properties(
  properties)
                  .required("recipeName")
                  .build())
          .build();
  
  GenerateContentConfig jsonConfig =
      GenerateContentConfig.builder()
          .responseMimeType("application/json")
          .responseSchema(recipeSchema)
          .build();
  
  List<InlinedRequest> inlineRequests =
      Arrays.asList(
          InlinedRequest.builder()
              .contents(
                  Arrays.asList(
                      Content.builder()
                          .role("user")
                          .parts(
                              Arrays.asList(
                                  Part.fromText(
                                      "List a few popular cookie recipes, and include the amounts of ingredients.")))
                          .build()))
              .config(jsonConfig)
              .build(),
          InlinedRequest.builder()
              .contents(
                  Arrays.asList(
                      Content.builder()
                          .role("user")
                          .parts(
                              Arrays.asList(
                                  Part.fromText(
                                      "List a few popular gluten free cookie recipes, and include the amounts of ingredients.")))
                          .build()))
              .config(jsonConfig)
              .build());
  
  BatchJob inlineBatchJob =
      client.batches.create(
          "gemini-3.8-flash",
          BatchJobSource.builder().inlinedRequests(inlineRequests).build(),
          CreateBatchJobConfig.builder().displayName("structured-output-job-1").build());
  
  // Wait for the job to finish
  String jobName = inlineBatchJob.name().get();
  System.out.println("Polling status for job: " + jobName);
  
  Set<JobState.Known> completedStates =
      new HashSet<>(Arrays.asList(
          JobState.Known.JOB_STATE_SUCCEEDED,
          JobState.Known.JOB_STATE_FAILED,
          JobState.Known.JOB_STATE_CANCELLED,
          JobState.Known.JOB_STATE_EXPIRED));
  
  BatchJob batchJobInline = client.batches.get(jobName, null);
  while (!completedStates.contains(batchJobInline.state().get().knownEnum())) {
    System.out.println(
        "Job not finished. Current state: " + batchJobInline.state().get() + ". Waiting 30 seconds...");
    Thread.sleep(30000);
    batchJobInline = client.batches.get(jobName, null);
  }
  
  System.out.println("Job finished with state: " + batchJobInline.state().get());
  
  // Print the response
  List<InlinedResponse> responses = batchJobInline.dest().get().inlinedResponses().orElse(Collections.emptyList());
  for (int i = 0; i < responses.size(); i++) {
    System.out.println("\n--- Response " + (i + 1) + " ---");
    InlinedResponse inlineResponse = responses.get(i);
    if (inlineResponse.response().isPresent()) {
      System.out.println(inlineResponse.response().get().text());
    }
  }
  ```

  {% /tab %}

{% /tabs %}

The following shows an example output of this job:

```json
--- Response 1 ---
[
  {
    "recipe_name": "Chocolate Chip Cookies",
    "ingredients": [
      "1 cup (2 sticks) unsalted butter, softened",
      "3/4 cup granulated sugar",
      "3/4 cup packed light brown sugar",
      "1 large egg",
      "1 teaspoon vanilla extract",
      "2 1/4 cups all-purpose flour",
      "1 teaspoon baking soda",
      "1/2 teaspoon salt",
      "1 1/2 cups chocolate chips"
    ]
  },
  {
    "recipe_name": "Oatmeal Raisin Cookies",
    "ingredients": [
      "1 cup (2 sticks) unsalted butter, softened",
      "1 cup packed light brown sugar",
      "1/2 cup granulated sugar",
      "2 large eggs",
      "1 teaspoon vanilla extract",
      "1 1/2 cups all-purpose flour",
      "1 teaspoon baking soda",
      "1 teaspoon ground cinnamon",
      "1/2 teaspoon salt",
      "3 cups old-fashioned rolled oats",
      "1 cup raisins"
    ]
  },
  {
    "recipe_name": "Sugar Cookies",
    "ingredients": [
      "1 cup (2 sticks) unsalted butter, softened",
      "1 1/2 cups granulated sugar",
      "1 large egg",
      "1 teaspoon vanilla extract",
      "2 3/4 cups all-purpose flour",
      "1 teaspoon baking powder",
      "1/2 teaspoon salt"
    ]
  }
]

--- Response 2 ---
[
  {
    "recipe_name": "Gluten-Free Chocolate Chip Cookies",
    "ingredients": [
      "1 cup (2 sticks) unsalted butter, softened",
      "3/4 cup granulated sugar",
      "3/4 cup packed light brown sugar",
      "2 large eggs",
      "1 teaspoon vanilla extract",
      "2 1/4 cups gluten-free all-purpose flour blend (with xanthan gum)",
      "1 teaspoon baking soda",
      "1/2 teaspoon salt",
      "1 1/2 cups chocolate chips"
    ]
  },
  {
    "recipe_name": "Gluten-Free Peanut Butter Cookies",
    "ingredients": [
      "1 cup (250g) creamy peanut butter",
      "1/2 cup (100g) granulated sugar",
      "1/2 cup (100g) packed light brown sugar",
      "1 large egg",
      "1 teaspoon vanilla extract",
      "1/2 teaspoon baking soda",
      "1/4 teaspoon salt"
    ]
  },
  {
    "recipe_name": "Gluten-Free Oatmeal Raisin Cookies",
    "ingredients": [
      "1/2 cup (1 stick) unsalted butter, softened",
      "1/2 cup granulated sugar",
      "1/2 cup packed light brown sugar",
      "1 large egg",
      "1 teaspoon vanilla extract",
      "1 cup gluten-free all-purpose flour blend",
      "1/2 teaspoon baking soda",
      "1/2 teaspoon ground cinnamon",
      "1/4 teaspoon salt",
      "1 1/2 cups gluten-free rolled oats",
      "1/2 cup raisins"
    ]
  }
]
```

## Monitoring job status  {% id="batch-job-status" %}

Use the operation name obtained when creating the batch job to poll its status.
The state field of the batch job will indicate its current status. A batch job
can be in one of the following states:

* `JOB_STATE_PENDING`: The job has been created and is waiting to be processed by the service.
* `JOB_STATE_RUNNING`: The job is in progress.
* `JOB_STATE_SUCCEEDED`: The job completed successfully. You can now retrieve the results.
* `JOB_STATE_FAILED`: The job failed. Check the error details for more information.
* `JOB_STATE_CANCELLED`: The job was cancelled by the user.
* `JOB_STATE_EXPIRED`: The job has expired because it was running or pending
  for more than 48 hours. The job will not have any results to retrieve.
  You can try submitting the job again or splitting up
  the requests into smaller batches.

You can poll the job status periodically to check for completion.

{% tabs %}
  {% tab label="Python" %}
  ```python
  import time
  from google import genai
  
  client = genai.Client()
  
  # Use the name of the job you want to check
  # e.g., inline_batch_job.name from the previous step
  job_name = "YOUR_BATCH_JOB_NAME" # (e.g. 'batches/your-batch-id')
  batch_job = client.batches.get(name=job_name)
  
  completed_states = set([
      'JOB_STATE_SUCCEEDED',
      'JOB_STATE_FAILED',
      'JOB_STATE_CANCELLED',
      'JOB_STATE_EXPIRED',
  ])
  
  print(f"Polling status for job: {job_name}")
  batch_job = client.batches.get(name=job_name) # Initial get
  while batch_job.state.name not in completed_states:
    print(f"Current state: {batch_job.state.name}")
    time.sleep(30) # Wait for 30 seconds before polling again
    batch_job = client.batches.get(name=job_name)
  
  print(f"Job finished with state: {batch_job.state.name}")
  if batch_job.state.name == 'JOB_STATE_FAILED':
      print(f"Error: {batch_job.error}")
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  // Use the name of the job you want to check
  // e.g., inlinedBatchJob.name from the previous step
  let batchJob;
  const completedStates = new Set([
      'JOB_STATE_SUCCEEDED',
      'JOB_STATE_FAILED',
      'JOB_STATE_CANCELLED',
      'JOB_STATE_EXPIRED',
  ]);
  
  try {
      batchJob = await ai.batches.get({name: inlinedBatchJob.name});
      while (!completedStates.has(batchJob.state || '')) {
          console.log(`Current state: ${batchJob.state}`);
          // Wait for 30 seconds before polling again
          await new Promise(resolve => setTimeout(resolve, 30000));
          batchJob = await client.batches.get({ name: batchJob.name });
      }
      console.log(`Job finished with state: ${batchJob.state}`);
      if (batchJob.state === 'JOB_STATE_FAILED') {
          // The exact structure of `error` might vary depending on the SDK
          // This assumes `error` is an object with a `message` property.
          console.error(`Error: ${batchJob.state}`);
      }
  } catch (error) {
      console.error(`An error occurred while polling job ${batchJob.name}:`, error);
  }
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import java.util.HashSet;
  import java.util.Arrays;
  import com.google.genai.Client;
  import com.google.genai.types.BatchJob;
  import com.google.genai.types.JobState;
  import java.util.Set;
  
  Client client = new Client();
  
  // Use the name of the job you want to check
  String jobName = "batches/your-batch-id";
  
  Set<JobState.Known> completedStates =
      new HashSet<>(Arrays.asList(
          JobState.Known.JOB_STATE_SUCCEEDED,
          JobState.Known.JOB_STATE_FAILED,
          JobState.Known.JOB_STATE_CANCELLED,
          JobState.Known.JOB_STATE_EXPIRED));
  
  System.out.println("Polling status for job: " + jobName);
  BatchJob batchJob = client.batches.get(jobName, null);
  while (!completedStates.contains(batchJob.state().get().knownEnum())) {
    System.out.println("Current state: " + batchJob.state().get());
    Thread.sleep(30000); // Wait for 30 seconds before polling again
    batchJob = client.batches.get(jobName, null);
  }
  
  System.out.println("Job finished with state: " + batchJob.state().get());
  if (batchJob.state().get().knownEnum() == JobState.Known.JOB_STATE_FAILED) {
    System.out.println("Error: " + batchJob.error().orElse(null));
  }
  ```

  {% /tab %}

{% /tabs %}

### Polling and webhooks  {% id="webhooks" %}

**Tired of polling?** Gemini now supports
[Webhooks](/docs/webhooks) for processing completions asynchronously.
Instead of continuously calling `GET / operations`, subscribe to
`batch.succeeded` directly to allow the Gemini API to push real-time
notifications to your server when asynchronous or long-running operations
complete.

{% tabs %}
  {% tab label="Python" %}
  ```python
  from google import genai
  
  client = genai.Client()
  
  webhook = client.webhooks.create(
      name="MyBatchWebhook",
      subscribed_events=["batch.succeeded", "batch.failed"],
      uri="https://my-api.com/gemini-callback",
  )
  
  print(f"Created webhook: {webhook.name}")
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  import { GoogleGenAI } from "@google/genai";
  
  const client = new GoogleGenAI();
  
  async function createWebhook() {
    const webhook = await client.webhooks.create({
      name: "MyBatchWebhook",
      subscribed_events: ["batch.succeeded", "batch.failed"],
      uri: "https://my-api.com/gemini-callback",
    });
  
    console.log(`Created webhook: ${webhook.name}`);
  }
  
  await createWebhook();
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import java.util.Arrays;
  import com.google.genai.Client;
  import com.google.genai.gaos.models.webhooks.Webhook;
  import com.google.genai.gaos.models.webhooks.WebhookInput;
  import com.google.genai.gaos.models.webhooks.WebhookSubscribedEvent;
  import java.util.List;
  
  Client client = new Client();
  
  WebhookInput input =
      WebhookInput.builder()
          .name("MyBatchWebhook")
          .subscribedEvents(
              Arrays.asList(
                  WebhookSubscribedEvent.BATCH_SUCCEEDED,
                  WebhookSubscribedEvent.BATCH_FAILED))
          .uri("https://my-api.com/gemini-callback")
          .build();
  
  Webhook webhook = client.webhooks.create(input).webhook().get();
  System.out.println("Created webhook: " + webhook.name().orElse(""));
  ```

  {% /tab %}

  {% tab label="REST" %}
  ```shell
  curl -X POST \
    "https://generativelanguage.googleapis.com/v1/webhooks?webhook_id=my-example-webhook-123" \
    -H "Content-Type: application/json" \
    -H "x-goog-api-key: $GOOGLE_API_KEY" \
    -d '{
      "name": "My Example Webhook",
      "uri": "https://my-api.com/gemini-callback",
      "subscribed_events": ["batch.succeeded", "batch.failed"]
    }'
  ```

  {% /tab %}

{% /tabs %}

## Retrieving results  {% id="retrieve-batch-results" %}

Once the job status indicates your batch job has succeeded, the results are
available in the `response` field.

{% tabs %}
  {% tab label="Python" %}
  ```python
  import json
  from google import genai
  
  client = genai.Client()
  
  # Use the name of the job you want to check
  # e.g., inline_batch_job.name from the previous step
  job_name = "YOUR_BATCH_JOB_NAME"
  batch_job = client.batches.get(name=job_name)
  
  if batch_job.state.name == 'JOB_STATE_SUCCEEDED':
  
      # If batch job was created with a file
      if batch_job.dest and batch_job.dest.file_name:
          # Results are in a file
          result_file_name = batch_job.dest.file_name
          print(f"Results are in file: {result_file_name}")
  
          print("Downloading result file content...")
          file_content = client.files.download(file=result_file_name)
          # Process file_content (bytes) as needed
          print(file_content.decode('utf-8'))
  
      # If batch job was created with inline request
      # (for embeddings, use batch_job.dest.inlined_embed_content_responses)
      elif batch_job.dest and batch_job.dest.inlined_responses:
          # Results are inline
          print("Results are inline:")
          for i, inline_response in enumerate(batch_job.dest.inlined_responses):
              print(f"Response {i+1}:")
              if inline_response.response:
                  # Accessing response, structure may vary.
                  try:
                      print(inline_response.response.text)
                  except AttributeError:
                      print(inline_response.response) # Fallback
              elif inline_response.error:
                  print(f"Error: {inline_response.error}")
      else:
          print("No results found (neither file nor inline).")
  else:
      print(f"Job did not succeed. Final state: {batch_job.state.name}")
      if batch_job.error:
          print(f"Error: {batch_job.error}")
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  // Use the name of the job you want to check
  // e.g., inlinedBatchJob.name from the previous step
  const jobName = "YOUR_BATCH_JOB_NAME";
  
  try {
      const batchJob = await ai.batches.get({ name: jobName });
  
      if (batchJob.state === 'JOB_STATE_SUCCEEDED') {
          console.log('Found completed batch:', batchJob.displayName);
          console.log(batchJob);
  
          // If batch job was created with a file destination
          if (batchJob.dest?.fileName) {
              const resultFileName = batchJob.dest.fileName;
              console.log(`Results are in file: ${resultFileName}`);
  
              console.log("Downloading result file content...");
              const fileContentBuffer = await ai.files.download({ file: resultFileName });
  
              // Process fileContentBuffer (Buffer) as needed
              console.log(fileContentBuffer.toString('utf-8'));
          }
  
          // If batch job was created with inline responses
          else if (batchJob.dest?.inlinedResponses) {
              console.log("Results are inline:");
              for (let i = 0; i < batchJob.dest.inlinedResponses.length; i++) {
                  const inlineResponse = batchJob.dest.inlinedResponses[i];
                  console.log(`Response ${i + 1}:`);
                  if (inlineResponse.response) {
                      // Accessing response, structure may vary.
                      if (inlineResponse.response.text !== undefined) {
                          console.log(inlineResponse.response.text);
                      } else {
                          console.log(inlineResponse.response); // Fallback
                      }
                  } else if (inlineResponse.error) {
                      console.error(`Error: ${inlineResponse.error}`);
                  }
              }
          }
  
          // If batch job was an embedding batch with inline responses
          else if (batchJob.dest?.inlinedEmbedContentResponses) {
              console.log("Embedding results found inline:");
              for (let i = 0; i < batchJob.dest.inlinedEmbedContentResponses.length; i++) {
                  const inlineResponse = batchJob.dest.inlinedEmbedContentResponses[i];
                  console.log(`Response ${i + 1}:`);
                  if (inlineResponse.response) {
                      console.log(inlineResponse.response);
                  } else if (inlineResponse.error) {
                      console.error(`Error: ${inlineResponse.error}`);
                  }
              }
          } else {
              console.log("No results found (neither file nor inline).");
          }
      } else {
          console.log(`Job did not succeed. Final state: ${batchJob.state}`);
          if (batchJob.error) {
              console.error(`Error: ${typeof batchJob.error === 'string' ? batchJob.error : batchJob.error.message || JSON.stringify(batchJob.error)}`);
          }
      }
  } catch (error) {
      console.error(`An error occurred while processing job ${jobName}:`, error);
  }
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import com.google.genai.Client;
  import com.google.genai.types.BatchJob;
  import com.google.genai.types.BatchJobDestination;
  import com.google.genai.types.InlinedEmbedContentResponse;
  import com.google.genai.types.InlinedResponse;
  import com.google.genai.types.JobState;
  import java.nio.file.Files;
  import java.nio.file.Paths;
  import java.util.List;
  
  Client client = new Client();
  
  // Use the name of the job you want to check
  String jobName = "batches/your-batch-id";
  BatchJob batchJob = client.batches.get(jobName, null);
  
  if (batchJob.state().get().knownEnum() == JobState.Known.JOB_STATE_SUCCEEDED) {
    BatchJobDestination dest = batchJob.dest().orElse(null);
  
    // If batch job was created with a file destination
    if (dest != null && dest.fileName().isPresent()) {
      String resultFileName = dest.fileName().get();
      System.out.println("Results are in file: " + resultFileName);
  
      System.out.println("Downloading result file content...");
      client.files.download(resultFileName, "batch_results.jsonl", null);
      String fileContent = Files.readString(Paths.get("batch_results.jsonl"));
      System.out.println(fileContent);
    }
    // If batch job was created with inline requests
    else if (dest != null && dest.inlinedResponses().isPresent()) {
      System.out.println("Results are inline:");
      List<InlinedResponse> responses = dest.inlinedResponses().get();
      for (int i = 0; i < responses.size(); i++) {
        System.out.println("Response " + (i + 1) + ":");
        InlinedResponse inlineResponse = responses.get(i);
        if (inlineResponse.response().isPresent()) {
          System.out.println(inlineResponse.response().get().text());
        } else if (inlineResponse.error().isPresent()) {
          System.out.println("Error: " + inlineResponse.error().get());
        }
      }
    }
    // If batch job was an embedding batch with inline responses
    else if (dest != null && dest.inlinedEmbedContentResponses().isPresent()) {
      System.out.println("Embedding results found inline:");
      List<InlinedEmbedContentResponse> responses = dest.inlinedEmbedContentResponses().get();
      for (int i = 0; i < responses.size(); i++) {
        System.out.println("Response " + (i + 1) + ":");
        InlinedEmbedContentResponse inlineResponse = responses.get(i);
        if (inlineResponse.response().isPresent()) {
          System.out.println(inlineResponse.response().get());
        } else if (inlineResponse.error().isPresent()) {
          System.out.println("Error: " + inlineResponse.error().get());
        }
      }
    } else {
      System.out.println("No results found (neither file nor inline).");
    }
  } else {
    System.out.println("Job did not succeed. Final state: " + batchJob.state().get());
    batchJob.error().ifPresent(err -> System.out.println("Error: " + err));
  }
  ```

  {% /tab %}

  {% tab label="REST" %}
  ```bash
  BATCH_NAME="batches/123456" # Your batch job name
  
  curl https://generativelanguage.googleapis.com/v1beta/$BATCH_NAME \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type:application/json" 2> /dev/null > batch_status.json
  
  if jq -r '.done' batch_status.json | grep -q "false"; then
      echo "Batch has not finished processing"
  fi
  
  batch_state=$(jq -r '.metadata.state' batch_status.json)
  if [[ $batch_state = "JOB_STATE_SUCCEEDED" ]]; then
      if [[ $(jq '.response | has("inlinedResponses")' batch_status.json) = "true" ]]; then
          jq -r '.response.inlinedResponses' batch_status.json
          exit
      fi
      responses_file_name=$(jq -r '.response.responsesFile' batch_status.json)
      curl https://generativelanguage.googleapis.com/download/v1beta/$responses_file_name:download?alt=media \
      -H "x-goog-api-key: $GEMINI_API_KEY" 2> /dev/null
  elif [[ $batch_state = "JOB_STATE_FAILED" ]]; then
      jq '.error' batch_status.json
  elif [[ $batch_state == "JOB_STATE_CANCELLED" ]]; then
      echo "Batch was cancelled by the user"
  elif [[ $batch_state == "JOB_STATE_EXPIRED" ]]; then
      echo "Batch expired after 48 hours"
  fi
  ```

  {% /tab %}

{% /tabs %}

## Listing batch jobs  {% id="listing-batch-jobs" %}

You can list your recent batch jobs.

{% tabs %}
  {% tab label="Python" %}
  ```python
  batch_jobs = client.batches.list()
  
  # Optional query config:
  # batch_jobs = client.batches.list(config={'page_size': 5})
  
  for batch_job in batch_jobs:
      print(batch_job)
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  const batchJobs = await ai.batches.list();
  
  // Optional query config:
  // const batchJobs = await ai.batches.list({config: {'pageSize': 5}});
  
  for await (const batchJob of batchJobs) {
      console.log(batchJob);
  }
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import com.google.genai.Client;
  import com.google.genai.Pager;
  import com.google.genai.types.BatchJob;
  import com.google.genai.types.ListBatchJobsConfig;
  
  Client client = new Client();
  
  Pager<BatchJob> batchJobs = client.batches.list(null);
  
  // Optional query config:
  // Pager<BatchJob> batchJobs = client.batches.list(ListBatchJobsConfig.builder().pageSize(5).build());
  
  for (BatchJob batchJob : batchJobs) {
    System.out.println(batchJob);
  }
  ```

  {% /tab %}

  {% tab label="REST" %}
  ```bash
  curl https://generativelanguage.googleapis.com/v1beta/batches \
  -H "x-goog-api-key: $GEMINI_API_KEY"
  ```

  {% /tab %}

{% /tabs %}

## Cancelling a batch job  {% id="canceling-batch-job" %}

You can cancel an ongoing batch job using its name. When a job is
canceled, it stops processing new requests.

{% tabs %}
  {% tab label="Python" %}
  ```python
  client.batches.cancel(name=batch_job_to_cancel.name)
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  await ai.batches.cancel({name: batchJobToCancel.name});
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import com.google.genai.Client;
  
  Client client = new Client();
  
  String batchJobToCancelName = "batches/your-batch-id";
  client.batches.cancel(batchJobToCancelName, null);
  ```

  {% /tab %}

  {% tab label="REST" %}
  ```bash
  BATCH_NAME="batches/123456" # Your batch job name
  
  # Cancel the batch
  curl https://generativelanguage.googleapis.com/v1beta/$BATCH_NAME:cancel \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  
  # Confirm that the status of the batch after cancellation is JOB_STATE_CANCELLED
  curl https://generativelanguage.googleapis.com/v1beta/$BATCH_NAME \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type:application/json" 2> /dev/null | jq -r '.metadata.state'
  ```

  {% /tab %}

{% /tabs %}

## Deleting a batch job  {% id="delete-batch-job" %}

You can delete an existing batch job using its name. When a job is
deleted, it stops processing new requests and is removed from the list of
batch jobs.

{% tabs %}
  {% tab label="Python" %}
  ```python
  client.batches.delete(name=batch_job_to_delete.name)
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  await ai.batches.delete({name: batchJobToDelete.name});
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import com.google.genai.Client;
  
  Client client = new Client();
  
  String batchJobToDeleteName = "batches/your-batch-id";
  client.batches.delete(batchJobToDeleteName, null);
  ```

  {% /tab %}

  {% tab label="REST" %}
  ```bash
  BATCH_NAME="batches/123456" # Your batch job name
  
  # Delete the batch job
  curl -X DELETE "https://generativelanguage.googleapis.com/v1beta/$BATCH_NAME" \
  -H "x-goog-api-key: $GEMINI_API_KEY"
  ```

  {% /tab %}

{% /tabs %}

## Generating images in batch  {% id="image-generation" %}

If you're using [Gemini Nano Banana](/docs/image-generation) and need to generate a lot
of images, you can use the Batch API to get higher
[rate limits](/docs/rate-limits) in exchange for a turnaround of up
to 24 hours.

You can either use [inline requests](/docs/batch-api#inline-requests-images) for small batches of requests (under 20MB) or
a [JSONL input file](/docs/batch-api#input-file-images) for large batches (recommended for image generation):

### Inline requests for images  {% id="inline-requests-images" %}

{% tabs %}
  {% tab label="Python" %}
  ```python
  import time
  import base64
  import json
  from google import genai
  from google.genai import types
  from PIL import Image
  
  client = genai.Client()
  
  # 1. Create batch job with inline requests
  inline_requests = [
      {
          'contents': [{'parts': [{'text': 'A big letter A surrounded by animals starting with the A letter'}]}],
          'config': {'response_modalities': ['TEXT', 'IMAGE']}
      },
      {
          'contents': [{'parts': [{'text': 'A big letter B surrounded by animals starting with the B letter'}]}],
          'config': {'response_modalities': ['TEXT', 'IMAGE']}
      }
  ]
  
  inline_batch_job = client.batches.create(
      model="gemini-3-pro-image-preview",
      src=inline_requests,
      config={
          'display_name': "inlined-image-requests-job-1",
      },
  )
  
  print(f"Created batch job: {inline_batch_job.name}")
  
  # 2. Monitor job status
  job_name = inline_batch_job.name
  print(f"Polling status for job: {job_name}")
  
  completed_states = set([
      'JOB_STATE_SUCCEEDED',
      'JOB_STATE_FAILED',
      'JOB_STATE_CANCELLED',
      'JOB_STATE_EXPIRED',
  ])
  
  batch_job = client.batches.get(name=job_name) # Initial get
  while batch_job.state.name not in completed_states:
    print(f"Current state: {batch_job.state.name}")
    time.sleep(10) # Wait for 10 seconds before polling again
    batch_job = client.batches.get(name=job_name)
  
  print(f"Job finished with state: {batch_job.state.name}")
  
  # 3. Retrieve results
  if batch_job.state.name == 'JOB_STATE_SUCCEEDED':
      print("Results are inline:")
      for i, inline_response in enumerate(batch_job.dest.inlined_responses):
          print(f"Response {i+1}:")
          if inline_response.response:
              for part in inline_response.response.candidates[0].content.parts:
                  if part.text:
                      print(part.text)
                  elif part.inline_data:
                      print(f"Image mime type: {part.inline_data.mime_type}")
                      image = part.as_image()
                      image.save(f"image_{i+1}.png")
          elif inline_response.error:
              print(f"Error: {inline_response.error}")
  elif batch_job.state.name == 'JOB_STATE_FAILED':
      print(f"Error: {batch_job.error}")
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  import {GoogleGenAI} from '@google/genai';
  
  const ai = new GoogleGenAI({});
  
  async function run() {
      // 1. Create batch job with inline requests
      const inlinedRequests = [
          {
              contents: [{parts: [{text: 'A big letter A surrounded by animals starting with the A letter'}]}],
              config: {responseModalities: ['TEXT', 'IMAGE']}
          },
          {
              contents: [{parts: [{text: 'A big letter B surrounded by animals starting with the B letter'}]}],
              config: {responseModalities: ['TEXT', 'IMAGE']}
          }
      ]
  
      const inlineBatchJob = await ai.batches.create({
          model: 'gemini-3-pro-image-preview',
          src: inlinedRequests,
          config: {
              displayName: 'inlined-image-requests-job-1',
          }
      });
  
      console.log(inlineBatchJob);
  
      // 2. Monitor job status
      let batchJob;
      const completedStates = new Set([
          'JOB_STATE_SUCCEEDED',
          'JOB_STATE_FAILED',
          'JOB_STATE_CANCELLED',
          'JOB_STATE_EXPIRED',
      ]);
  
      try {
          batchJob = await ai.batches.get({name: inlineBatchJob.name || ''});
          while (!completedStates.has(batchJob.state || '')) {
              console.log(`Current state: ${batchJob.state}`);
              // Wait for 10 seconds before polling again
              await new Promise(resolve => setTimeout(resolve, 10000));
              batchJob = await ai.batches.get({ name: batchJob.name || '' });
          }
          console.log(`Job finished with state: ${batchJob.state}`);
      } catch (error) {
          console.error(`An error occurred while polling job ${inlineBatchJob.name}:`, error);
          return;
      }
  
      // 3. Retrieve results
      if (batchJob.state === 'JOB_STATE_SUCCEEDED') {
          if (batchJob.dest?.inlinedResponses) {
              console.log("Results are inline:");
              for (let i = 0; i < batchJob.dest.inlinedResponses.length; i++) {
                  const inlineResponse = batchJob.dest.inlinedResponses[i];
                  console.log(`Response ${i + 1}:`);
                  if (inlineResponse.response) {
                      for (const part of inlineResponse.response?.candidates?.[0]?.content?.parts || []) {
                          if (part.text) {
                              console.log(part.text);
                          } else if (part.inlineData) {
                              console.log(`Image mime type: ${part.inlineData.mimeType}`);
                          }
                      }
                  } else if (inlineResponse.error) {
                      console.error(`Error: ${inlineResponse.error}`);
                  }
              }
          } else {
              console.log("No inline results found.");
          }
      } else if (batchJob.state === 'JOB_STATE_FAILED') {
           console.error(`Error: ${typeof batchJob.error === 'string' ? batchJob.error : batchJob.error?.message || JSON.stringify(batchJob.error)}`);
      }
  }
  await run();
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import java.util.HashSet;
  import java.util.Arrays;
  import java.util.Collections;
  import com.google.genai.Client;
  import com.google.genai.types.BatchJob;
  import com.google.genai.types.BatchJobSource;
  import com.google.genai.types.Content;
  import com.google.genai.types.CreateBatchJobConfig;
  import com.google.genai.types.GenerateContentConfig;
  import com.google.genai.types.InlinedRequest;
  import com.google.genai.types.InlinedResponse;
  import com.google.genai.types.JobState;
  import com.google.genai.types.Part;
  import java.nio.file.Files;
  import java.nio.file.Paths;
  import java.util.List;
  import java.util.Set;
  
  Client client = new Client();
  
  // 1. Create batch job with inline requests
  GenerateContentConfig imageConfig =
      GenerateContentConfig.builder().responseModalities("TEXT", "IMAGE").build();
  
  List<InlinedRequest> inlineRequests =
      Arrays.asList(
          InlinedRequest.builder()
              .contents(
                  Arrays.asList(
                      Content.fromParts(
                          Part.fromText(
                              "A big letter A surrounded by animals starting with the A letter"))))
              .config(imageConfig)
              .build(),
          InlinedRequest.builder()
              .contents(
                  Arrays.asList(
                      Content.fromParts(
                          Part.fromText(
                              "A big letter B surrounded by animals starting with the B letter"))))
              .config(imageConfig)
              .build());
  
  BatchJob inlineBatchJob =
      client.batches.create(
          "gemini-3-pro-image-preview",
          BatchJobSource.builder().inlinedRequests(inlineRequests).build(),
          CreateBatchJobConfig.builder().displayName("inlined-image-requests-job-1").build());
  
  System.out.println("Created batch job: " + inlineBatchJob.name().orElse(""));
  
  // 2. Monitor job status
  String jobName = inlineBatchJob.name().get();
  System.out.println("Polling status for job: " + jobName);
  
  Set<JobState.Known> completedStates =
      new HashSet<>(Arrays.asList(
          JobState.Known.JOB_STATE_SUCCEEDED,
          JobState.Known.JOB_STATE_FAILED,
          JobState.Known.JOB_STATE_CANCELLED,
          JobState.Known.JOB_STATE_EXPIRED));
  
  BatchJob batchJob = client.batches.get(jobName, null);
  while (!completedStates.contains(batchJob.state().get().knownEnum())) {
    System.out.println("Current state: " + batchJob.state().get());
    Thread.sleep(10000); // Wait for 10 seconds before polling again
    batchJob = client.batches.get(jobName, null);
  }
  
  System.out.println("Job finished with state: " + batchJob.state().get());
  
  // 3. Retrieve results
  if (batchJob.state().get().knownEnum() == JobState.Known.JOB_STATE_SUCCEEDED) {
    System.out.println("Results are inline:");
    List<InlinedResponse> responses = batchJob.dest().get().inlinedResponses().orElse(Collections.emptyList());
    for (int i = 0; i < responses.size(); i++) {
      System.out.println("Response " + (i + 1) + ":");
      InlinedResponse inlineResponse = responses.get(i);
      if (inlineResponse.response().isPresent()) {
        for (Part part : inlineResponse.response().get().parts()) {
          if (part.text().isPresent()) {
            System.out.println(part.text().get());
          } else if (part.inlineData().isPresent()) {
            System.out.println("Image mime type: " + part.inlineData().get().mimeType().orElse(""));
            Files.write(
                Paths.get("image_" + (i + 1) + ".png"),
                part.inlineData().get().data().get());
          }
        }
      } else if (inlineResponse.error().isPresent()) {
        System.out.println("Error: " + inlineResponse.error().get());
      }
    }
  } else if (batchJob.state().get().knownEnum() == JobState.Known.JOB_STATE_FAILED) {
    System.out.println("Error: " + batchJob.error().orElse(null));
  }
  ```

  {% /tab %}

  {% tab label="REST" %}
  ```bash
  # 1. Create batch job
  printf -v request_data '{
      "batch": {
          "display_name": "my-batch-image-requests",
          "input_config": {
              "requests": {
                  "requests": [
                      {
                          "request": {
                              "contents": [{"parts": [{"text": "A big letter A surrounded by animals starting with the A letter"}]}],
                              "generation_config": {"responseModalities": ["TEXT", "IMAGE"]}
                          },
                          "metadata": { "key": "request-1" }
                      },
                      {
                          "request": {
                              "contents": [{"parts": [{"text": "A big letter B surrounded by animals starting with the B letter"}]}],
                              "generation_config": {"responseModalities": ["TEXT", "IMAGE"]}
                          },
                          "metadata": { "key": "request-2" }
                      }
                  ]
              }
          }
      }
  }'
  curl https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image-preview:batchGenerateContent \
    -H "x-goog-api-key: $GEMINI_API_KEY" \
    -X POST \
    -H "Content-Type:application/json" \
    -d "$request_data" > created_batch.json
  
  BATCH_NAME=$(jq -r '.name' created_batch.json)
  echo "Created batch job: $BATCH_NAME"
  
  # 2. Poll job status until completion by repeating the following command
  # Replace $BATCH_NAME with the name returned above.
  curl https://generativelanguage.googleapis.com/v1beta/$BATCH_NAME \
    -H "x-goog-api-key: $GEMINI_API_KEY" \
    -H "Content-Type:application/json" > batch_status.json
  
  echo "Current status:"
  jq '.' batch_status.json
  
  # 3. If state is JOB_STATE_SUCCEEDED, retrieve results from batch_status.json
  batch_state=$(jq -r '.state' batch_status.json)
  if [[ $batch_state = "JOB_STATE_SUCCEEDED" ]]; then
      echo "Job succeeded. Results:"
      jq -r '.dest.inlinedResponses' batch_status.json
  fi
  ```

  {% /tab %}

{% /tabs %}

### Input file for images  {% id="input-file-images" %}

{% tabs %}
  {% tab label="Python" %}
  ```python
  import json
  import time
  import base64
  from google import genai
  from google.genai import types
  from PIL import Image
  
  client = genai.Client()
  
  # 1. Create and upload file
  file_name = "my-batch-image-requests.jsonl"
  with open(file_name, "w") as f:
      requests = [
          {"key": "request-1", "request": {"contents": [{"parts": [{"text": "A big letter A surrounded by animals starting with the A letter"}]}], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]}}},
          {"key": "request-2", "request": {"contents": [{"parts": [{"text": "A big letter B surrounded by animals starting with the B letter"}]}], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]}}}
      ]
      for req in requests:
          f.write(json.dumps(req) + "\n")
  
  uploaded_file = client.files.upload(
      file=file_name,
      config=types.UploadFileConfig(display_name='my-batch-image-requests', mime_type='jsonl')
  )
  print(f"Uploaded file: {uploaded_file.name}")
  
  # 2. Create batch job
  file_batch_job = client.batches.create(
      model="gemini-3-pro-image-preview",
      src=uploaded_file.name,
      config={
          'display_name': "file-image-upload-job-1",
      },
  )
  print(f"Created batch job: {file_batch_job.name}")
  
  # 3. Monitor job status
  job_name = file_batch_job.name
  print(f"Polling status for job: {job_name}")
  
  completed_states = set([
      'JOB_STATE_SUCCEEDED',
      'JOB_STATE_FAILED',
      'JOB_STATE_CANCELLED',
      'JOB_STATE_EXPIRED',
  ])
  
  batch_job = client.batches.get(name=job_name) # Initial get
  while batch_job.state.name not in completed_states:
    print(f"Current state: {batch_job.state.name}")
    time.sleep(10) # Wait for 10 seconds before polling again
    batch_job = client.batches.get(name=job_name)
  
  print(f"Job finished with state: {batch_job.state.name}")
  
  # 4. Retrieve results
  if batch_job.state.name == 'JOB_STATE_SUCCEEDED':
      result_file_name = batch_job.dest.file_name
      print(f"Results are in file: {result_file_name}")
      print("Downloading result file content...")
      file_content_bytes = client.files.download(file=result_file_name)
      file_content = file_content_bytes.decode('utf-8')
      # The result file is also a JSONL file. Parse and print each line.
      for line in file_content.splitlines():
        if line:
          parsed_response = json.loads(line)
          if 'response' in parsed_response and parsed_response['response']:
              for part in parsed_response['response']['candidates'][0]['content']['parts']:
                if part.get('text'):
                  print(part['text'])
                elif part.get('inlineData'):
                  print(f"Image mime type: {part['inlineData']['mimeType']}")
                  data = base64.b64decode(part['inlineData']['data'])
          elif 'error' in parsed_response:
              print(f"Error: {parsed_response['error']}")
  elif batch_job.state.name == 'JOB_STATE_FAILED':
      print(f"Error: {batch_job.error}")
  ```

  {% /tab %}

  {% tab label="JavaScript" %}
  ```javascript
  import {GoogleGenAI} from '@google/genai';
  import * as fs from "fs";
  import * as path from "path";
  
  const ai = new GoogleGenAI({});
  
  async function run() {
      // 1. Create and upload file
      const fileName = "my-batch-image-requests.jsonl";
      const requests = [
          { "key": "request-1", "request": { "contents": [{ "parts": [{ "text": "A big letter A surrounded by animals starting with the A letter" }] }], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]} } },
          { "key": "request-2", "request": { "contents": [{ "parts": [{ "text": "A big letter B surrounded by animals starting with the B letter" }] }], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]} } }
      ];
      const filePath = fileName;
  
      try {
          const writeStream = fs.createWriteStream(filePath, { flags: 'w' });
          for (const req of requests) {
              writeStream.write(JSON.stringify(req) + '\n');
          }
          writeStream.end();
          console.log(`Successfully wrote batch requests to ${filePath}`);
      } catch (error) {
          console.error(`An unexpected error occurred writing file:`, error);
          return;
      }
  
      const uploadedFile = await ai.files.upload({file: fileName, config: { mimeType: 'jsonl' }});
      console.log(`Uploaded file: ${uploadedFile.name}`);
  
      // 2. Create batch job
      const fileBatchJob = await ai.batches.create({
          model: 'gemini-3-pro-image-preview',
          src: uploadedFile.name || '',
          config: {
              displayName: 'file-image-upload-job-1',
          }
      });
      console.log(fileBatchJob);
  
      // 3. Monitor job status
      let batchJob;
      const completedStates = new Set([
          'JOB_STATE_SUCCEEDED',
          'JOB_STATE_FAILED',
          'JOB_STATE_CANCELLED',
          'JOB_STATE_EXPIRED',
      ]);
  
      try {
          batchJob = await ai.batches.get({name: fileBatchJob.name || ''});
          while (batchJob.state && !completedStates.has(batchJob.state)) {
              console.log(`Current state: ${batchJob.state}`);
              // Wait for 10 seconds before polling again
              await new Promise(resolve => setTimeout(resolve, 10000));
              batchJob = await ai.batches.get({ name: batchJob.name || '' });
          }
          console.log(`Job finished with state: ${batchJob.state}`);
      } catch (error) {
          console.error(`An error occurred while polling job ${fileBatchJob.name}:`, error);
          return;
      }
  
      // 4. Retrieve results
      if (batchJob.state === 'JOB_STATE_SUCCEEDED') {
          if (batchJob.dest?.fileName) {
              const resultFileName = batchJob.dest.fileName;
              console.log(`Results are in file: ${resultFileName}`);
              console.log("Downloading result file content...");
              await ai.files.download({ file: resultFileName, downloadPath: 'result.jsonl' });
              const fileContent = fs.readFileSync('result.jsonl', 'utf-8');
              for (const line of fileContent.split('\n')) {
                  if (line) {
                      const parsedResponse = JSON.parse(line);
                      if (parsedResponse.response) {
                          for (const part of parsedResponse.response.candidates[0].content.parts) {
                              if (part.text) {
                                  console.log(part.text);
                              } else if (part.inlineData) {
                                  console.log(`Image mime type: ${part.inlineData.mimeType}`);
                              }
                          }
                      } else if (parsedResponse.error) {
                          console.error(`Error: ${parsedResponse.error}`);
                      }
                  }
              }
          } else {
              console.log("No result file found.");
          }
      } else if (batchJob.state === 'JOB_STATE_FAILED') {
           console.error(`Error: ${typeof batchJob.error === 'string' ? batchJob.error : batchJob.error?.message || JSON.stringify(batchJob.error)}`);
      }
  }
  await run();
  ```

  {% /tab %}

  {% tab label="Java" %}
  ```java
  import java.util.HashSet;
  import java.util.Arrays;
  import com.google.genai.Client;
  import com.google.genai.types.BatchJob;
  import com.google.genai.types.BatchJobSource;
  import com.google.genai.types.CreateBatchJobConfig;
  import com.google.genai.types.File;
  import com.google.genai.types.JobState;
  import com.google.genai.types.UploadFileConfig;
  import java.nio.file.Files;
  import java.nio.file.Paths;
  import java.util.List;
  import java.util.Set;
  
  Client client = new Client();
  
  // 1. Create and upload file
  String fileName = "my-batch-image-requests.jsonl";
  List<String> requests =
      Arrays.asList(
          "{\"key\": \"request-1\", \"request\": {\"contents\": [{\"parts\": [{\"text\": \"A big letter A surrounded by animals starting with the A letter\"}]}], \"generation_config\": {\"responseModalities\": [\"TEXT\", \"IMAGE\"]}}}",
          "{\"key\": \"request-2\", \"request\": {\"contents\": [{\"parts\": [{\"text\": \"A big letter B surrounded by animals starting with the B letter\"}]}], \"generation_config\": {\"responseModalities\": [\"TEXT\", \"IMAGE\"]}}}");
  Files.write(Paths.get(fileName), requests);
  
  File uploadedFile =
      client.files.upload(
          fileName,
          UploadFileConfig.builder()
              .displayName("my-batch-image-requests")
              .mimeType("jsonl")
              .build());
  System.out.println("Uploaded file: " + uploadedFile.name().orElse(""));
  
  // 2. Create batch job
  BatchJob fileBatchJob =
      client.batches.create(
          "gemini-3-pro-image-preview",
          BatchJobSource.builder().fileName(uploadedFile.name().get()).build(),
          CreateBatchJobConfig.builder().displayName("file-image-upload-job-1").build());
  System.out.println("Created batch job: " + fileBatchJob.name().orElse(""));
  
  // 3. Monitor job status
  String jobName = fileBatchJob.name().get();
  System.out.println("Polling status for job: " + jobName);
  
  Set<JobState.Known> completedStates =
      new HashSet<>(Arrays.asList(
          JobState.Known.JOB_STATE_SUCCEEDED,
          JobState.Known.JOB_STATE_FAILED,
          JobState.Known.JOB_STATE_CANCELLED,
          JobState.Known.JOB_STATE_EXPIRED));
  
  BatchJob batchJob = client.batches.get(jobName, null);
  while (!completedStates.contains(batchJob.state().get().knownEnum())) {
    System.out.println("Current state: " + batchJob.state().get());
    Thread.sleep(10000); // Wait for 10 seconds before polling again
    batchJob = client.batches.get(jobName, null);
  }
  
  System.out.println("Job finished with state: " + batchJob.state().get());
  
  // 4. Retrieve results
  if (batchJob.state().get().knownEnum() == JobState.Known.JOB_STATE_SUCCEEDED) {
    String resultFileName = batchJob.dest().get().fileName().get();
    System.out.println("Results are in file: " + resultFileName);
    System.out.println("Downloading result file content...");
    client.files.download(resultFileName, "batch_image_results.jsonl", null);
    for (String line : Files.readAllLines(Paths.get("batch_image_results.jsonl"))) {
      if (!line.isEmpty()) {
        System.out.println(line);
      }
    }
  } else if (batchJob.state().get().knownEnum() == JobState.Known.JOB_STATE_FAILED) {
    System.out.println("Error: " + batchJob.error().orElse(null));
  }
  ```

  {% /tab %}

  {% tab label="REST" %}
  ```bash
  # 1. Create and upload file
  echo '{"key": "request-1", "request": {"contents": [{"parts": [{"text": "A big letter A surrounded by animals starting with the A letter"}]}], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]}}}' > my-batch-image-requests.jsonl
  echo '{"key": "request-2", "request": {"contents": [{"parts": [{"text": "A big letter B surrounded by animals starting with the B letter"}]}], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]}}}' >> my-batch-image-requests.jsonl
  
  # Follow File API guide to upload: https://ai.google.dev/docs/files#upload_a_file
  # This example assumes you have uploaded the file and set BATCH_INPUT_FILE to its name (e.g., files/abcdef123)
  BATCH_INPUT_FILE="files/your-uploaded-file-name"
  
  # 2. Create batch job
  printf -v request_data '{
      "batch": {
          "display_name": "my-batch-file-image-requests",
          "input_config": { "file_name": "%s" }
      }
  }' "$BATCH_INPUT_FILE"
  curl https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image-preview:batchGenerateContent \
    -X POST \
    -H "x-goog-api-key: $GEMINI_API_KEY" \
    -H "Content-Type:application/json" \
    -d "$request_data" > created_batch.json
  
  BATCH_NAME=$(jq -r '.name' created_batch.json)
  echo "Created batch job: $BATCH_NAME"
  
  # 3. Poll job status until completion by repeating the following command:
  curl https://generativelanguage.googleapis.com/v1beta/$BATCH_NAME \
    -H "x-goog-api-key: $GEMINI_API_KEY" \
    -H "Content-Type:application/json" > batch_status.json
  
  echo "Current status:"
  jq '.' batch_status.json
  
  # 4. If state is JOB_STATE_SUCCEEDED, download results file
  batch_state=$(jq -r '.state' batch_status.json)
  if [[ $batch_state = "JOB_STATE_SUCCEEDED" ]]; then
      responses_file_name=$(jq -r '.dest.fileName' batch_status.json)
      echo "Job succeeded. Downloading results from $responses_file_name..."
      curl https://generativelanguage.googleapis.com/download/v1beta/$responses_file_name:download?alt=media \
        -H "x-goog-api-key: $GEMINI_API_KEY" > batch_results.jsonl
      echo "Results saved to batch_results.jsonl"
  fi
  ```

  {% /tab %}

{% /tabs %}

## Technical details  {% id="technical-details" %}

*   **Supported models:** Batch API supports a range of Gemini models.
  Refer to the [Models page](/docs/models) for each model's support
  of Batch API. The supported modalities for Batch API are the same
  as what's supported on the interactive (or non-batch) API.

*   **Pricing:** Batch API usage is priced at 50% of the standard interactive
  API cost for the equivalent model. See the [pricing page](/docs/pricing)
  for details. Refer to the [rate limits page](/docs/rate-limits#batch-api)
  for details on rate limits for this feature.

*   **Service level objective (SLO):** Batch jobs are designed to complete
  within a 24-hour turnaround time. Many jobs may complete much faster
  depending on their size and current system load.

*   **Caching:** [Context caching](/docs/caching) is supported
  for batch requests. Reuse cached content by specifying the `cached_content`
  resource name in the configuration of individual requests within your batch.
  If a request in your batch results in a cache hit, you pay the
  [standard context caching rates](/docs/pricing).


## Best practices  {% id="best-practices" %}

*   **Use input files for large requests:** For a large number of requests,
  always use the file input
  method for better manageability and to avoid hitting request size limits for
  the [`BatchGenerateContent`](https://ai.google.dev/api/batch-mode#google.ai.generativelanguage.v1beta.BatchService.BatchGenerateContent)
  call itself. Note that there's a 2GB file size limit per input file.

*   **Error handling:** Check the `batchStats` for `failedRequestCount` after a
  job completes. If using file output, parse each line to check if it's a
  `GenerateContentResponse` or a status object indicating an error for that
  specific request. See the [troubleshooting
  guide](/docs/troubleshooting#error-codes) for a complete set of
  error codes.

*   **Submit jobs once:** The creation of a batch job is not idempotent.
  If you send the same creation request twice, two separate batch jobs will
  be created.

*   **Break up very large batches:** While the target turnaround time is 24
  hours, actual processing time can vary based on system load and job size.
  For large jobs, consider breaking them into smaller
  batches if intermediate results are needed sooner.


## What's next  {% id="whats-next" %}

*   Check out the [Batch API notebook](https://colab.research.google.com/github/google-gemini/cookbook/blob/main/quickstarts/Batch_mode.ipynb)
  for more examples.

*   The OpenAI compatibility layer supports Batch API. Read the examples on the
  [OpenAI Compatibility](/docs/openai#batch) page.


