Skip to main content
Spring AI is the official AI framework for the Spring ecosystem. It provides a consistent API for working with various AI providers, including OpenAI-compatible APIs like Lumenfall.

Installation

Add the Spring AI OpenAI dependency to your project:

Maven

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
Add the Spring AI BOM to your <dependencyManagement>:
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Gradle

implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter'
Add the BOM:
dependencyManagement {
    imports {
        mavenBom "org.springframework.ai:spring-ai-bom:1.0.0"
    }
}

Configuration

Configure Lumenfall in your application.properties or application.yml:

application.properties

spring.ai.openai.api-key=lmnfl_your_api_key
spring.ai.openai.base-url=https://api.lumenfall.ai/openai
spring.ai.openai.image.options.model=gemini-3-pro-image

application.yml

spring:
  ai:
    openai:
      api-key: ${LUMENFALL_API_KEY}
      base-url: https://api.lumenfall.ai/openai
      image:
        options:
          model: gemini-3-pro-image

Generate images

Using OpenAiImageModel

import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.openai.OpenAiImageModel;
import org.springframework.ai.openai.OpenAiImageOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ImageGenerationService {

    private final OpenAiImageModel imageModel;

    @Autowired
    public ImageGenerationService(OpenAiImageModel imageModel) {
        this.imageModel = imageModel;
    }

    public String generateImage(String prompt) {
        ImageResponse response = imageModel.call(
            new ImagePrompt(prompt)
        );

        return response.getResult().getOutput().getUrl();
    }
}

With custom options

import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.openai.OpenAiImageModel;
import org.springframework.ai.openai.OpenAiImageOptions;
import org.springframework.stereotype.Service;

@Service
public class ImageGenerationService {

    private final OpenAiImageModel imageModel;

    public ImageGenerationService(OpenAiImageModel imageModel) {
        this.imageModel = imageModel;
    }

    public String generateImage(String prompt, String model, String size) {
        OpenAiImageOptions options = OpenAiImageOptions.builder()
            .model(model)
            .quality("hd")
            .n(1)
            .height(1024)
            .width(1024)
            .responseFormat("url")
            .style("natural")
            .build();

        ImageResponse response = imageModel.call(
            new ImagePrompt(prompt, options)
        );

        return response.getResult().getOutput().getUrl();
    }
}

Generate multiple images

public List<String> generateMultipleImages(String prompt, int count) {
    OpenAiImageOptions options = OpenAiImageOptions.builder()
        .model("gpt-image-1.5")
        .n(count)
        .build();

    ImageResponse response = imageModel.call(
        new ImagePrompt(prompt, options)
    );

    return response.getResults().stream()
        .map(result -> result.getOutput().getUrl())
        .collect(Collectors.toList());
}

REST Controller example

import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.openai.OpenAiImageModel;
import org.springframework.ai.openai.OpenAiImageOptions;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/images")
public class ImageController {

    private final OpenAiImageModel imageModel;

    public ImageController(OpenAiImageModel imageModel) {
        this.imageModel = imageModel;
    }

    @PostMapping("/generate")
    public ImageGenerationResponse generate(@RequestBody ImageGenerationRequest request) {
        OpenAiImageOptions options = OpenAiImageOptions.builder()
            .model(request.model() != null ? request.model() : "gemini-3-pro-image")
            .n(1)
            .height(1024)
            .width(1024)
            .build();

        ImageResponse response = imageModel.call(
            new ImagePrompt(request.prompt(), options)
        );

        return new ImageGenerationResponse(
            response.getResult().getOutput().getUrl()
        );
    }
}

record ImageGenerationRequest(String prompt, String model) {}
record ImageGenerationResponse(String url) {}

Edit images

For image editing, use the OpenAI client directly since Spring AI’s image editing support may vary:
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.*;
import org.springframework.stereotype.Service;
import java.nio.file.Path;

@Service
public class ImageEditService {

    private final OpenAIClient client;

    public ImageEditService() {
        this.client = OpenAIOkHttpClient.builder()
            .apiKey(System.getenv("LUMENFALL_API_KEY"))
            .baseUrl("https://api.lumenfall.ai/openai/v1")
            .build();
    }

    public String editImage(Path imagePath, String prompt) {
        ImagesResponse response = client.images().edit(ImageEditParams.builder()
            .model("gpt-image-1.5")
            .image(imagePath)
            .prompt(prompt)
            .n(1)
            .size(ImageEditParams.Size._1024X1024)
            .build());

        return response.data().get(0).url().orElse(null);
    }
}

Async image generation

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class AsyncImageService {

    private final OpenAiImageModel imageModel;

    public AsyncImageService(OpenAiImageModel imageModel) {
        this.imageModel = imageModel;
    }

    @Async
    public CompletableFuture<String> generateImageAsync(String prompt) {
        OpenAiImageOptions options = OpenAiImageOptions.builder()
            .model("gemini-3-pro-image")
            .build();

        ImageResponse response = imageModel.call(
            new ImagePrompt(prompt, options)
        );

        return CompletableFuture.completedFuture(
            response.getResult().getOutput().getUrl()
        );
    }
}

Error handling

import org.springframework.ai.retry.RetryUtils;
import org.springframework.web.client.RestClientException;

@Service
public class ImageGenerationService {

    private final OpenAiImageModel imageModel;

    public ImageGenerationService(OpenAiImageModel imageModel) {
        this.imageModel = imageModel;
    }

    public String generateImageSafely(String prompt) {
        try {
            ImageResponse response = imageModel.call(
                new ImagePrompt(prompt)
            );
            return response.getResult().getOutput().getUrl();
        } catch (RestClientException e) {
            if (e.getMessage().contains("401")) {
                throw new RuntimeException("Invalid API key");
            } else if (e.getMessage().contains("429")) {
                throw new RuntimeException("Rate limit exceeded");
            } else if (e.getMessage().contains("402")) {
                throw new RuntimeException("Insufficient balance");
            }
            throw new RuntimeException("Image generation failed: " + e.getMessage());
        }
    }
}

Configuration properties

PropertyDescriptionDefault
spring.ai.openai.api-keyLumenfall API key-
spring.ai.openai.base-urlAPI base URLhttps://api.openai.com
spring.ai.openai.image.options.modelDefault modelgemini-3-pro-image
spring.ai.openai.image.options.qualityImage qualitystandard
spring.ai.openai.image.options.nNumber of images1
spring.ai.openai.image.options.response-formatResponse formaturl
spring.ai.openai.image.options.styleImage stylevivid

Next steps