OpenAI Image Generation Application with Spring Boot

Dennis Brysiuk
NEW IT Engineering
Published in
9 min readJan 20, 2023
OpenAI generated Image

In this article, I will demonstrate the creation of a simple Spring Boot application that utilizes the OpenAI-API to generate images from natural language descriptions.

All of the provided code examples can be accessed on GitHub

OpenAI

Before we begin, it is important to familiarize ourselves with the OpenAI. OpenAI is a one of the leading provider of tools and APIs for Artificial Intelligence and Machine Learning. Lets take a look at the different APIs and tools provided by OpenAI that can help add AI capabilities to applications:

  • GPT-3 API: allows to generate natural language text. This API can be used for tasks such as text generation, summarization, and question answering.
  • DALL-E API: allows to generate images from natural language descriptions. This API can be used for tasks such as image generation, object detection, and image classification to applications such as game development and e-commerce.
  • OpenAI Playground: is a web-based interface that allows to experiment with different models and parameters. This tool can be used to test different models and fine-tune their parameters.
  • Language API: allows to access a variety of natural language processing models for tasks such as text generation, summarization, and question answering. This API can be used to add natural language processing capabilities to applications such as chatbots and virtual assistants.
  • Vision API: allows to access a variety of image processing models for tasks such as image classification, object detection, and image generation. This API can be used to add image processing capabilities to applications such as security cameras and object recognition systems.
  • Translation API: allows to translate text from one language to another. This API can be used to add multilingual capabilities to applications.
  • Completion API: allows to complete text based on a given prompt or context. This API can be used to add predictive typing capabilities to applications such as text editors and messaging systems.
  • Formality API: allows to classify text as either formal or informal. This API can be used to ensure that text generated by the application is appropriate for the intended audience.

In this article, we will be limiting our examination to the image generation capabilities of the DALL-E API, in order to stay within the scope.

Spring Boot Application

Now that we know the relevant information about OpenAI, we can start creating our Spring Boot application. Spring Boot is a framework that allows us to build Java applications quickly and easily by doing many of the configuration tasks automatically.

1. Dependencies

First, let’s create a new Spring Boot project using the Spring Initializer with following dependencies:

  • spring-boot-starter-web: includes all the necessary libraries to create a web application (RESTful web service)
  • spring-cloud-starter-openfeign: it allows us to easily create HTTP client for calling DALL-E API.
  • lombok: it help us to reduce amount of boilerplate code that we need to write and maintain, and making our code more concise and readable.
Spring Initializr

In addition, the following dependency is required for the example, which can be added via maven oder gradle:

  • validation-api: a Java Bean Validation specification’s API that provides a way of defining constraints on the model and validation them.

more details about validation-api see chapter 2. Data Tranfer Objects.

To enable the use of OpenFeign in the project, it is necessary to add the @EnableFeignClients annotation to the Spring Boot Application class. This annotation enables the scanning of Feign clients in the project and allows them to be used.

@SpringBootApplication
@EnableFeignClients
public class ImageGeneratorServiceApplication {

public static void main(String[] args) {
SpringApplication.run(ImageGeneratorServiceApplication.class, args);
}

}

2. Data Transfer Objects

Data Transfer Objects (DTOs) are used to transfer data between layers of an application, in our case, between the presentation layer and the business logic layer. They provide a simple, lightweight representation of the data and can include validation for the data being transferred.

Let’s create GenerateImageRequest and GenerateImageResponse classes that represent the requiest and response objects for the image generation API.

@Data
public class GenerateImageRequest {

@NotBlank
private String prompt;

@ValidSize
private String size;

@Min(1)
@Max(10)
@JsonProperty("num_images")
private int numImages;

}

The GenerateImageRequest class contains fields for the prompt, size, and number of images to generate. It is annotated with @Data which is provided by Lombok library to automatically generate getters, setters, equals, hashCode, toString, and constructors for all fields in the class.

The following annotations from validation-api dependency are used in the class for input validation:

  • @NotBlank ensures prompt is not null or empty
  • @ValidSize custom validation for image size field (valid sizes for DALL-E API are 256x256, 512x512 and 1024x1024 pixel)
  • @Min(1) and @Max(10) ensures numImages has a value within a range (valid number of generating of images for one request is 1 to 10 )

Custom Validation

@Documented
@Constraint(validatedBy = SizeValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidSize {

String message() default "Invalid value. This is not a valid size.";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

}
public class SizeValidator implements ConstraintValidator<ValidSize, String> {

private static final List<String> validSizes = Arrays.asList("1024x1024", "512x512", "256x256");

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return validSizes.contains(value);
}

}

In the example code provided a custom validator called @ValidSize. It is composed of two parts: the constraint annotation @ValidSize and the validator class SizeValidator. The validator checks if the value of the field is present in a predefined list of valid sizes (1024x1024, 512x512 and 256x256). If it’s not present, it returns false. The message in the annotation is for providing a custom error message in case of validation failure.

I will not delve deeper into this topic as the implementation of custom annotations is not the focus of this article.

@Data
public class GenerateImageResponse {

private List<GeneratedImage> data;

}
@Data
public class GeneratedImage {

private String url;

}

The GenerateImageResponse class that holds the response data of image generation. The field data is a list of GeneratedImage. GeneratedImage class contains one field url which hold the URL of the generated image from DALL-E API. Both classes are annotated with @Data which is provided by Lombok library to automatically generate getters, setters, equals, hashCode, toString, and constructors for all fields in the class.

3. Configurations

By using confiugrations in the application, we ca write more robust, flexible and easy to maintain applications that can be easily adapted to different environments and requirements.

Let’s add the configuration settings for the OpenAI image generator API to application.yml file.

openai:
image-generator:
url: https://api.openai.com
api-key: <YOUR-API-KEY>
  • url: the base URL for the OpenAI API
  • api-key: the API key that is used to authenticate the application with the OpenAI API.

To generate an API key for OpenAI, you must first create an account on their website, then add a payment method, and finally, you can generate your API key under the “API Keys” section in the “User” menu. To view the pricing information for OpenAI, you can visit their website. Don’t worry about the cost, as after adding your billing information, you will receive free credits for 3–4 months, which is equivalent to around $18.

Finally, we will create a Spring Boot Configuration class to configure the client by providing an API Key for authentication:

@Configuration
public class ImageGeneratorConfig {

@Value("${openai.image-generator.api-key}")
private String apiKey;

@Bean
public RequestInterceptor apiKeyInterceptor() {
return template -> template.header("Authorization", "Bearer " + apiKey);
}
}

The class is annotated with @Configuration, which indicates that it is a Spring configuration class. The class contains a single method apiKeyInterceptor which is annotated with @Bean. This method returns a RequestInterceptor that is used to add an Authorization header to the request with the value of "Bearer " + apiKey.

4. Feign Client

As already mentioned, Feign is a declarative web service client that makes it easier to write web service clients in Java.

@FeignClient(name = "imageGenerator", url = "${openai.image-generator.url}", configuration = ImageGeneratorConfig.class)
public interface ImageGeneratorClient {

@PostMapping(value = "/v1/images/generations")
GenerateImageResponse generateImage(@RequestBody final GenerateImageRequest request);

}

The ImageGeneratorClient interface is annotated with @FeignClient, which is used to specify the name and URL of the API. The name attribute specifies the name of the client, and the url attribute specifies the URL of the API (in that case we are using the url from the application.yml file configurations). The configuration attribute is used to specify the configuration class for the client (in that case we are using created configuration class ImageGeneratorConfig to provide the authorizations to the client).

The ImageGeneratorClient interface also contains a method generateImage which is annotated with @PostMapping annotation, to specify the endpoint for generating images and it takes and return already created DTOs classes GenerateImageRequest and GenerateImageResponse .

5. Service

In Spring Boot, the @Service annotation is used to mark a class as a service layer component, indicating that it contains business logic and is eligible for autodetection by the Spring context.

Currently, we do not require any business logic as the required validation and settings have already been implemented through validators and configurations. However, to ensure a clean microservice architecture, we will create a class for the service layer in which business logic can be added in the future, such as caching of the images.

public interface ImageGeneratorService {

GenerateImageResponse generateImage(GenerateImageRequest generateImageRequest);

}
@Service
@AllArgsConstructor
public class ImageGeneratorServiceImpl implements ImageGeneratorService {

private final ImageGeneratorClient imageGeneratorClient;

public GenerateImageResponse generateImage(final GenerateImageRequest request) {
final GenerateImageResponse response = imageGeneratorClient.generateImage(request);
// if necessary, the response can be further processed here

return response;
}

}

Service interface ImageGeneratorService with implementation ImageGeneratorServiceImpl is used to separate the business logic of an application from its implementation details. This separation of concerns allows for more maintainable, testable, and flexible code.

The ImageGeneratorService is responsible for calling the generateImage method of the ImageGeneratorClient and processing the response if necessary. The ImageGeneratorServiceImpl is an implementation of the ImageGeneratorService which is annotated with @Service and @AllArgsConstructor, indicating that it is a Spring service bean and that it has a constructor that takes in a single parameter of type ImageGeneratorClient.

6. Rest Controller

In Spring Boot, the @RestController annotation is used to mark a class as a RESTful web service controller.

In last step we need to create a REST controller that maps HTTP requests to the generateImage method of the ImageGeneratorService :

@RestController
@RequestMapping("/images")
@AllArgsConstructor
public class ImageGeneratorController {

private static final Logger LOG = LoggerFactory.getLogger(ImageGeneratorController.class);

private final ImageGeneratorService imageGeneratorService;

@PostMapping("generate")
public ResponseEntity<GenerateImageResponse> generateImage(@Valid @RequestBody final GenerateImageRequest request) {
LOG.info("Received image generation request: {}", request);
final GenerateImageResponse response = imageGeneratorService.generateImage(request);
LOG.info("Generated image: {}", response);
return ResponseEntity.ok(response);
}

}

The class is annotated with @RestController and @RequestMapping("/images"), indicating that it is a REST controller and that it handles requests at the "/images" endpoint. The generateImage method is annotated with @PostMapping("generate"), indicating that it handles POST requests at the "/images/generate" endpoint. The method takes in a single parameter of type GenerateImageRequest and returns a GenerateImageResponse with a list of generated image urls.

Ready to use

There are several ways to run a Spring Boot application locally:

  • Using the Spring Boot Maven Plugin by running the following command in the root directory of your project:
mvn spring-boot:run
  • Using the Spring Boot Gradle Plugin by running the following command in the root directory of your project:
./gradlew bootRun
  • Using an IDE such as Eclipse, IntelliJ IDEA, or Spring Tool Suite by import the project and then run the main class of your application as a Java application.

After the application is running create a POST request to the endpoint https://localhost:8080/images/generate with request body of GenerateImageRequest class. There are several programs that can be used, such as cURL:

curl -X POST -H "Content-Type: application/json" -d '{"prompt":"robot saying thank you","size":"512x512","num_images":1}' http://localhost:8080/images/generate

or Postman:

POST Request wiht Postman

In summary, this article has provided an overview of how to create a Spring Boot application that utilizes the OpenAI API to generate images based on text prompts, while also highlighting best practices for microservices architecture, and the importance of using libraries like OpenFeign and Lombok to simplify the interaction with external APIs, and Bean Validation to ensure the validity of requests before they are sent to the API. With this in mind, I wish you happy coding!

OpenAI generated Image

--

--