Introduction to Java Web API Integration

Welcome to the fifth lesson of our course on building an image generation service with Java! In our previous lessons, we've built several key components of our application: the PromptManager for formatting user inputs, the ImageManager for storing and processing images, the ImageGeneratorService for connecting to Google's Gemini API, and, most recently, the ImageGeneratorController, which handles input validation and response formatting.

Now it's time to bring everything together by creating a Java web application that will expose our functionality through HTTP endpoints. This is the final piece of our backend architecture that will allow users to interact with our image generation service through a web interface.

In the previous lesson, we built the ImageGeneratorController, which acts as an intermediary between our service layer and the API endpoints we'll create today. The controller handles the business logic of validating inputs, calling the appropriate service methods, and formatting responses. Now, we'll create the web endpoints that will receive HTTP requests from clients and pass them to our controller.

Our Java web API will have three main endpoints:

  1. An endpoint to serve the main response at /
  2. An endpoint to handle image generation requests
  3. An endpoint to retrieve all previously generated images

By the end of this lesson, you'll have a complete Java web API that integrates with our controller, providing a clean interface for clients to generate and retrieve images.

Setting Up the Java Web Application

Let's start by setting up our Java web application. In this lesson, we'll use Spring Boot, a popular Java framework for building web applications. Spring Boot simplifies the process of creating stand-alone, production-grade web services with minimal configuration.

To include Spring Boot in your Gradle project, add the following to your build.gradle file:

plugins {
    id 'org.springframework.boot' version '3.5.9'
    id 'io.spring.dependency-management' version '1.1.5'
    id 'java'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    // Add other dependencies as needed
}

This configuration applies the Spring Boot plugin and includes the necessary dependencies for building a web application with Spring Boot.

Create the main application class in src/main/java/com/codesignal/Main.java:

package com.codesignal;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

This class uses the @SpringBootApplication annotation to enable Spring Boot's auto-configuration and component scanning. The main method starts the embedded web server and launches the application.

With this setup, your Java application is ready to define web endpoints and serve HTTP requests.

@Controller vs @RestController

Before defining endpoints, it's important to understand two common Spring annotations:

  • @Controller is typically used for MVC applications that render views such as HTML templates.
  • @RestController is shorthand for @Controller + @ResponseBody, which means returned values are written directly to the HTTP response body.

That distinction matters because a plain String returned from a @Controller method is usually treated as a view name, not as response text. In this unit, we want our root route to return a plain string and our API endpoints to return JSON, so @RestController is the better fit.

Building the ImageGeneratorController

We'll start by creating the Spring-aware ImageGeneratorController that contains our application logic and the root endpoint. This is the same controller role we introduced in the previous unit, now registered as a Spring bean so the web layer can use it directly.

package com.codesignal.controllers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.codesignal.services.ImageGeneratorService;

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

@RestController
public class ImageGeneratorController {
    private final ImageGeneratorService imageGeneratorService;

    @Autowired
    public ImageGeneratorController(ImageGeneratorService imageGeneratorService) {
        this.imageGeneratorService = imageGeneratorService;
    }

    @GetMapping("/")
    public String index() {
        return "image_generator";
    }

    public ResponseEntity<?> generateImage(String userInput, String aspectRatio) {
        if (userInput == null || userInput.isEmpty()) {
            Map<String, String> error = new HashMap<>();
            error.put("error", "Missing input");
            return ResponseEntity.badRequest().body(error);
        }
        try {
            String finalAspectRatio = (aspectRatio == null || aspectRatio.isEmpty()) ? "16:9" : aspectRatio;
            String base64Image = imageGeneratorService.generateImage(userInput, finalAspectRatio);
            Map<String, String> response = new HashMap<>();
            response.put("image", base64Image);
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            Map<String, String> error = new HashMap<>();
            error.put("error", e.getMessage());
            return ResponseEntity.internalServerError().body(error);
        }
    }

    public ResponseEntity<?> getImages() {
        try {
            List<Map<String, Object>> images = imageGeneratorService.getAllImages();
            Map<String, Object> response = new HashMap<>();
            response.put("images", images);
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            Map<String, String> error = new HashMap<>();
            error.put("error", e.getMessage());
            return ResponseEntity.internalServerError().body(error);
        }
    }
}

A few important notes about this class:

  • The root route / is mapped directly with @GetMapping("/").
  • Because this class uses @RestController, returning "image_generator" sends that string as the HTTP response body.
  • The generateImage() and getImages() methods are not mapped directly to URLs here. Instead, they are regular controller methods that our REST endpoint class will delegate to.

This keeps our validation and response formatting logic in one place while making the HTTP routing layer very small.

Defining API Endpoints

Now that we have our Spring-managed application controller, let's define the endpoints that will handle client requests.

In Spring Boot, endpoints are defined in controller classes using annotations such as @RestController, @GetMapping, and @PostMapping. These annotations map HTTP requests to Java methods, allowing you to handle different types of requests and URLs.

We'll use a separate ImageGeneratorRestController to expose the /api routes and delegate to the ImageGeneratorController we just built.

Generate Image Endpoint

Let's define the endpoint for generating images:

package com.codesignal.controllers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/api")
public class ImageGeneratorRestController {

    private final ImageGeneratorController imageGeneratorController;

    @Autowired
    public ImageGeneratorRestController(ImageGeneratorController imageGeneratorController) {
        this.imageGeneratorController = imageGeneratorController;
    }

    @PostMapping("/generate_image")
    public ResponseEntity<?> generateImage(@RequestBody Map<String, String> payload) {
        String userInput = payload.get("user_input");
        String aspectRatio = payload.getOrDefault("aspect_ratio", "16:9");
        return imageGeneratorController.generateImage(userInput, aspectRatio);
    }
}

Here, we use the @RestController annotation to indicate that this class will handle REST API requests and return JSON responses. The @RequestMapping("/api") annotation sets a base path for all endpoints in this class.

The @PostMapping("/generate_image") annotation maps POST requests to /api/generate_image to the generateImage() method. The @RequestBody annotation tells Spring to parse the incoming JSON request body into a Map<String, String>. We extract both user_input and aspect_ratio from the map and pass them to our ImageGeneratorController's generateImage method, which handles the business logic and returns a response.

Under the hood, this call flows through to ImageGeneratorService, which issues a generateContent request to the official Gemini API using the gemini-3.1-flash-image model and parses the returned inlineData image bytes.

Get Images Endpoint

Next, let's define the endpoint for retrieving all previously generated images:

@GetMapping("/get_images")
public ResponseEntity<?> getImages() {
    return imageGeneratorController.getImages();
}

This method is mapped to GET requests at /api/get_images using the @GetMapping annotation. It simply calls the getImages() method of our controller, which retrieves all stored images and returns them as a JSON response.

Full REST Controller Implementation
package com.codesignal.controllers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/api")
public class ImageGeneratorRestController {

    private final ImageGeneratorController imageGeneratorController;

    @Autowired
    public ImageGeneratorRestController(ImageGeneratorController imageGeneratorController) {
        this.imageGeneratorController = imageGeneratorController;
    }

    @PostMapping("/generate_image")
    public ResponseEntity<?> generateImage(@RequestBody Map<String, String> requestBody) {
        String userInput = requestBody.get("user_input");
        String aspectRatio = requestBody.getOrDefault("aspect_ratio", "16:9");
        return imageGeneratorController.generateImage(userInput, aspectRatio);
    }

    @GetMapping("/get_images")
    public ResponseEntity<?> getImages() {
        return imageGeneratorController.getImages();
    }
}

With these three endpoints, our Java web API provides a complete interface for clients to interact with our image generation service. Clients can confirm the application is running, generate new images, and retrieve previously generated ones.

Configuring Spring Boot

Now that we have our Spring Boot application and endpoints defined, let's configure the server to run our application.

By default, Spring Boot uses an embedded Tomcat server that listens on port 8080. If you want to change the port or other server settings, you can do so in the src/main/resources/application.properties file:

server.port=3000
server.address=0.0.0.0
  • server.port=3000 tells Spring Boot to listen on port 3000 for incoming requests.
  • server.address=0.0.0.0 makes the server accessible on all network interfaces.

Once the application is running, you should see output similar to:

Tomcat started on port(s): 3000 (http) with context path ''
Started Main in 2.345 seconds (JVM running for 2.789)

You can now access your application by opening a web browser and navigating to http://localhost:3000/.

Summary and Practice Preview

In this lesson, we've built a complete Java web API using Spring Boot that integrates with our ImageGeneratorController to provide a web interface for our image generation service. Let's review what we've accomplished:

  1. We set up a Spring Boot application with the necessary configuration, including port and host settings.
  2. We clarified the difference between @Controller and @RestController, and used @RestController because this unit returns response bodies rather than rendered templates.
  3. We created an ImageGeneratorController that handles the root route plus the core image-generation and retrieval logic.
  4. We implemented a POST endpoint for generating images, which extracts user_input and aspect_ratio from the request and passes them to our controller.
  5. We set up a GET endpoint for retrieving all previously generated images.
  6. We configured the embedded web server to run on a specific port and listen on all network interfaces.

Our Java web API now provides a complete interface for clients to interact with our image generation service. The API endpoints receive HTTP requests, extract the necessary data, and pass it to our controller, which handles the business logic of validating inputs, applying a default aspect ratio when needed, calling the appropriate service methods, and formatting responses.

This completes the server-side portion of our image generation application. We've built a robust, modular architecture with a clear separation of concerns:

  • The PromptManager handles prompt formatting.
  • The ImageManager manages image storage and processing.
  • The ImageGeneratorService sends generateContent requests to the Gemini API using the gemini-3.1-flash-image model and parses the resulting inlineData image bytes.
  • The ImageGeneratorController validates inputs and formats responses.
  • The ImageGeneratorRestController maps client HTTP requests to the appropriate controller methods.
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