Introduction to the Image Generator Service

Welcome to the third lesson of our course on building an image generation service in Java! In our previous lessons, we created the PromptManager to format user inputs into detailed prompts and the ImageManager to handle storing and processing generated images. Now, we're ready to build the core component that brings everything together: the ImageGeneratorService.

The ImageGeneratorService is the central piece of our application that will:

  1. Connect to Google's Gemini API to generate images.
  2. Use our PromptManager to format user inputs into effective prompts.
  3. Pass the selected aspect ratio through to the prompt template.
  4. Store generated images using our ImageManager.
  5. Provide access to all previously generated images.

This service acts as the bridge between our application's components and the external AI service that actually creates the images. By encapsulating all the image generation logic in a dedicated service class, we maintain a clean separation of concerns in our application architecture.

In this lesson, we'll implement this service step by step, from setting up the client to handling responses and errors. By the end, you'll have a fully functional image generation service that you can later integrate into a Java web application.

Setting Up the Gemini API Client

Before we can generate images, we need to set up a way to communicate with Google's Gemini API. We'll use the Gemini Java SDK to call the generateContent flow with the gemini-3.1-flash-image model.

To do this, you'll need the following dependencies in your project:

dependencies {
    implementation 'com.google.genai:google-genai:0.1.0'
}

These libraries let us call the API through the SDK and work with typed response objects instead of building raw HTTP requests by hand.

Next, let's create our ImageGeneratorService class and set up the service configuration in the constructor. In Java, you would typically place this class in a package such as com.codesignal.services and save it in a file named ImageGeneratorService.java:

package com.codesignal.services;

import com.codesignal.models.ImageManager;
import com.google.genai.Client;
import com.google.genai.types.HttpOptions;

public class ImageGeneratorService {
    private ImageManager imageManager;
    private String apiKey;
    private String baseUrl;
    private Client client;

    public ImageGeneratorService() {
        this.imageManager = new ImageManager();
        this.apiKey = System.getenv("GEMINI_API_KEY");
        this.baseUrl = System.getenv("GEMINI_BASE_URL");
        this.client = new Client.Builder()
                .apiKey(apiKey)
                .httpOptions(HttpOptions.builder().baseUrl(baseUrl).apiVersion("v1beta").build())
                .build();
    }
}

In this constructor, we're doing several important things:

  1. Creating an instance of our ImageManager class to handle storing and retrieving images.
  2. Reading the Gemini API key and base URL from environment variables.
  3. Building a Gemini SDK client that we can reuse for image generation requests.

In Java, environment variables can be accessed using System.getenv("VARIABLE_NAME"). This approach keeps sensitive information like API keys out of your source code.

Implementing the Image Generation Logic with the SDK

Now that we have our service configuration set up, let's implement the core method of our service: generateImage(). This method will take a userInput string and an aspectRatio string, format them into a detailed prompt using our PromptManager, call the Gemini API through the SDK, and store the resulting image using our ImageManager.

Here is the implementation:

package com.codesignal.services;

import com.codesignal.models.ImageManager;
import com.codesignal.models.PromptManager;
import com.google.genai.Client;
import com.google.genai.types.GenerateContentConfig;
import com.google.genai.types.GenerateContentResponse;
import com.google.genai.types.HttpOptions;
import com.google.genai.types.Part;

import java.util.Base64;
import java.util.List;
import java.util.Map;

public class ImageGeneratorService {
    private ImageManager imageManager;
    private String apiKey;
    private String baseUrl;
    private Client client;

    public ImageGeneratorService() {
        this.imageManager = new ImageManager();
        this.apiKey = System.getenv("GEMINI_API_KEY");
        this.baseUrl = System.getenv("GEMINI_BASE_URL");
        this.client = new Client.Builder()
                .apiKey(apiKey)
                .httpOptions(HttpOptions.builder().baseUrl(baseUrl).apiVersion("v1beta").build())
                .build();
    }

    public String generateImage(String userInput, String aspectRatio) throws Exception {
        String prompt = PromptManager.formatPrompt(userInput, aspectRatio);

        GenerateContentResponse response = client.models.generateContent(
                "gemini-3.1-flash-image",
                prompt,
                GenerateContentConfig.builder()
                        .responseModalities(List.of("IMAGE"))
                        .build()
        );

        byte[] imageBytes = null;
        for (Part part : response.candidates().get().get(0).content().get().parts().get()) {
            if (part.inlineData().isPresent()) {
                String base64Data = part.inlineData().get().data()
                        .orElseThrow(() -> new RuntimeException("No data found"));
                imageBytes = Base64.getDecoder().decode(base64Data);
                break;
            }
        }

        if (imageBytes == null) {
            throw new RuntimeException("No image data found in the response.");
        }

        return imageManager.addImage(prompt, imageBytes);
    }

    public List<Map<String, Object>> getAllImages() {
        return imageManager.getImages();
    }
}

Let's break down what's happening in this method:

  1. We call PromptManager.formatPrompt(userInput, aspectRatio) to convert the user's input into a detailed prompt using our predefined template.
  2. We build the request to the Gemini API using the gemini-3.1-flash-image model.
  3. We set responseModalities to ["IMAGE"] so the model returns image content.
  4. We inspect the returned candidates[0].content.parts list and look for a part that includes inlineData, which is where the generated image bytes are returned.
  5. We decode the returned image data from Base64 into raw bytes.
  6. We pass the prompt and imageBytes to our ImageManager's addImage() method, which stores the image and returns the Base64 string.

The method returns the Base64-encoded image data, which can be used directly in web applications.

Error Handling and Service Integration

Generating images through an external API can fail for various reasons: network issues, API limits, invalid prompts, or server errors. To make our service robust, we include checks for missing image data and throw clear exceptions when needed.

There are also Gemini-specific failure cases to be aware of. The generateContent response may return successfully but not include an image inlineData part — for example, if the model returns only text or if the content was filtered. Our code handles this explicitly by checking whether imageBytes is still null after iterating through all parts, and throwing a RuntimeException with a clear message in that case.

Now, let's add one more method to our service to retrieve all previously generated images:

public List<Map<String, Object>> getAllImages() {
    return imageManager.getImages();
}

This simple method delegates to our ImageManager's getImages() method, returning the complete list of stored images along with their associated prompts and IDs.

With these two methods, our ImageGeneratorService provides a complete interface for generating and retrieving images. The service integrates our previously built components (PromptManager and ImageManager) and connects them to the external Gemini API using the Gemini Java SDK.

ImageGeneratorService Complete Implementation
package com.codesignal.services;

import com.codesignal.models.ImageManager;
import com.codesignal.models.PromptManager;
import com.google.genai.Client;
import com.google.genai.types.GenerateContentConfig;
import com.google.genai.types.GenerateContentResponse;
import com.google.genai.types.HttpOptions;
import com.google.genai.types.Part;

import java.util.Base64;
import java.util.List;
import java.util.Map;

public class ImageGeneratorService {
    private ImageManager imageManager;
    private String apiKey;
    private String baseUrl;
    private Client client;

    public ImageGeneratorService() {
        this.imageManager = new ImageManager();
        this.apiKey = System.getenv("GEMINI_API_KEY");
        this.baseUrl = System.getenv("GEMINI_BASE_URL");
        this.client = new Client.Builder()
                .apiKey(apiKey)
                .httpOptions(HttpOptions.builder().baseUrl(baseUrl).apiVersion("v1beta").build())
                .build();
    }

    public String generateImage(String userInput, String aspectRatio) throws Exception {
        String prompt = PromptManager.formatPrompt(userInput, aspectRatio);
        GenerateContentResponse response = client.models.generateContent(
                "gemini-3.1-flash-image",
                prompt,
                GenerateContentConfig.builder()
                        .responseModalities(List.of("IMAGE"))
                        .build()
        );

        byte[] imageBytes = null;
        for (Part part : response.candidates().get().get(0).content().get().parts().get()) {
            if (part.inlineData().isPresent()) {
                String base64Data = part.inlineData().get().data()
                        .orElseThrow(() -> new RuntimeException("No data found"));
                imageBytes = Base64.getDecoder().decode(base64Data);
                break;
            }
        }

        if (imageBytes == null) {
            throw new RuntimeException("No image data found in the response.");
        }

        return imageManager.addImage(prompt, imageBytes);
    }

    public List<Map<String, Object>> getAllImages() {
        return imageManager.getImages();
    }
}
Testing the Complete Service

Now that we've implemented our ImageGeneratorService, let's create a test class to verify that it works correctly. You can do this with a simple main method in a Java class, such as Main.java:

package com.codesignal;

import com.codesignal.services.ImageGeneratorService;

import java.util.List;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        String userInput = "Luxury Tech Conference 2025: Innovating the Future - April 10th, New York City";
        String aspectRatio = "16:9";

        try {
            ImageGeneratorService imageService = new ImageGeneratorService();
            String serviceResult = imageService.generateImage(userInput, aspectRatio);
            System.out.println("Image Generated Successfully:");
            System.out.println(serviceResult);

            System.out.println("\nAll Stored Images:");
            List<Map<String, Object>> images = imageService.getAllImages();
            for (Map<String, Object> image : images) {
                System.out.println(image);
            }
        } catch (Exception e) {
            System.out.println("Error generating image: " + e.getMessage());
        }
    }
}

In this test code, we:

  1. Import our ImageGeneratorService class.
  2. Define a sample userInput and aspectRatio for testing.
  3. Create an instance of our ImageGeneratorService.
  4. Call the generateImage() method with our sample input.
  5. Print the result.
  6. Retrieve and print all stored images.

When running this code with a valid API key and the correct dependencies, you would see output similar to:

Image Generated Successfully:
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a...

All Stored Images:
{id=0, prompt=# ROLE
Lead graphic designer
..., image_base64=/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a...}

The Base64 string would be much longer in a real application, but it has been truncated here for readability. This string represents the encoded image data returned by the Gemini API in the inlineData.data field.

Summary and Practice Preview

In this lesson, we've built the ImageGeneratorService, the core component of our image generation application. This service connects our previously built components (PromptManager and ImageManager) to Google's Gemini API using the Gemini Java SDK, calling the official generateContent flow with the gemini-3.1-flash-image model to generate high-quality images from text prompts.

Let's review what we've learned:

  1. We set up the Gemini Java SDK to communicate with Google's generateContent API in Java.
  2. We implemented the generateImage() method, calling the generateContent flow with the gemini-3.1-flash-image model and configuring image output via responseModalities.
  3. We passed the selected aspectRatio through the prompt formatting step.
  4. We parsed the Gemini response by navigating to candidates[0].content.parts and extracting the image bytes from the inlineData.data field.
  5. We added robust error handling to deal with potential API issues.
  6. We created a method to retrieve all previously generated images.
  7. We tested our service with a sample prompt.

The ImageGeneratorService is a crucial piece of our application architecture. It encapsulates all the logic related to image generation, providing a clean interface for other components to use. In the next lesson, we'll build a controller that will use this service to handle HTTP requests in our Java web application.

In the upcoming practice exercises, you'll have the opportunity to work with the ImageGeneratorService, testing its functionality with different prompts and exploring how it integrates with the rest of our application. You'll also get to experiment with error handling and see how the service behaves in various scenarios.

Remember that to use this service in a real application, you'll need to:

  1. Add the Gemini Java SDK dependency to your Java project.
  2. Obtain a valid API key from Google.
  3. Set the GEMINI_API_KEY environment variable to your API key and GEMINI_BASE_URL to the appropriate Gemini endpoint.

With the ImageGeneratorService in place, we're one step closer to having a complete image generation web application!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal