OpenAIJavaStructured OutputsAI

AI-Powered Text Generation in Java: Using OpenAI's API for Structured Outputs

By Arapsih Güngör
Picture of the author
Published on
Illustration of Java code in a Spring Boot application integrating with OpenAI's API to produce structured JSON outputs.

AI-Powered Text Generation in Java: Using OpenAI's API for Structured Outputs

The OpenAI API offers powerful capabilities for natural language understanding and generation. One of its standout features is the ability to produce structured outputs that conform to a specified JSON schema. In this blog post, we'll explore how to integrate the OpenAI API into a Java Spring Boot application to generate structured data. We'll walk through a practical example of creating a daily schedule based on user input, demonstrating how to harness the API's structured output feature for reliable and predictable responses.


Benefits of Using OpenAI API for Structured Outputs

  • Reliable Data Formatting: Ensures that API responses adhere to a predefined JSON schema.
  • Ease of Parsing: Structured outputs are easier to parse and integrate into applications.
  • Reduced Error Handling: Minimizes the need for extensive validation and error checking.
  • Flexibility: Allows dynamic generation of structured data based on user input.

Setting Up the Java Spring Boot Project

Before we begin, ensure you have the following:

  • Java 17 installed
  • Maven for dependency management.
  • An OpenAI API key. You can obtain one by signing up at OpenAI's website.

Including Necessary Dependencies

Create a new Spring Boot project or add the following dependencies to your pom.xml:

<!-- Spring Boot Starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

<!-- Spring Web (includes RestTemplate) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Jackson Databind for JSON processing -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>


Implementing the OpenAI API Client with RestTemplate

Here's a Spring service that interacts with the OpenAI API to generate a daily schedule based on user input, utilizing structured outputs.

Important Update

When working with the OpenAI API's structured outputs, it's crucial to include all required fields in your JSON schema. In particular, the json_schema object within the response_format must include the name field; otherwise, you'll encounter an error like:

"message": "Missing required parameter: 'response_format.json_schema.name'.",

Let's ensure our code includes this field and that our JSON schema is valid.

package com.example.openai;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

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


@Component
public class OpenAIService {

    private static final String OPENAI_API_KEY = "YOUR_OPENAI_API_KEY";
    private static final String OPENAI_API_URL = "https://api.openai.com/v1/chat/completions";
    private static final String MODEL_NAME = "gpt-4o-mini"; // Ensure the model supports structured outputs

    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper;

    public OpenAIService() {
        this.restTemplate = new RestTemplate();
        this.objectMapper = new ObjectMapper();
    }

    public String generateDailyPlan(String userPreferences) throws Exception {
        // Build the request payload
        Map<String, Object> payload = new HashMap<>();
        payload.put("model", MODEL_NAME);

        // Messages
        List<Map<String, String>> messages = new ArrayList<>();
        messages.add(Map.of("role", "system", "content",
                            "You are a helpful assistant that creates a structured daily plan in JSON format."));
        messages.add(Map.of("role", "user", "content",
                            "Create a daily plan for me based on the following preferences:\n" + userPreferences));

        payload.put("messages", messages);

        // Define the JSON schema for structured output
        Map<String, Object> jsonSchema = new HashMap<>();
        jsonSchema.put("type", "object");
        Map<String, Object> properties = new HashMap<>();
        Map<String, Object> scheduleProperty = new HashMap<>();
        scheduleProperty.put("type", "array");

        Map<String, Object> items = new HashMap<>();
        items.put("type", "object");
        Map<String, Object> itemProperties = new HashMap<>();
        itemProperties.put("time", Map.of("type", "string"));
        itemProperties.put("activity", Map.of("type", "string"));
        items.put("properties", itemProperties);
        items.put("required", List.of("time", "activity"));
        items.put("additionalProperties", false);

        scheduleProperty.put("items", items);
        properties.put("schedule", scheduleProperty);

        jsonSchema.put("properties", properties);
        jsonSchema.put("required", List.of("schedule"));
        jsonSchema.put("additionalProperties", false);

        // Add response format with JSON schema
        Map<String, Object> responseFormat = new HashMap<>();
        responseFormat.put("type", "json_schema");

        // Include 'name' and 'strict' in 'json_schema' as required by the API
        Map<String, Object> jsonSchemaFormat = new HashMap<>();
        jsonSchemaFormat.put("name", "daily_plan_response");
        jsonSchemaFormat.put("schema", jsonSchema);
        jsonSchemaFormat.put("strict", true);

        responseFormat.put("json_schema", jsonSchemaFormat);
        payload.put("response_format", responseFormat);

        // Set headers
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setBearerAuth(OPENAI_API_KEY);

        // Create the request entity
        HttpEntity<String> requestEntity = new HttpEntity<>(
                objectMapper.writeValueAsString(payload), headers);

        // Send the request and receive the response
        ResponseEntity<String> responseEntity = restTemplate.exchange(
                OPENAI_API_URL, HttpMethod.POST, requestEntity, String.class);

        // Parse the assistant's response
        JsonNode responseJson = objectMapper.readTree(responseEntity.getBody());
        String assistantContent = responseJson
                .path("choices")
                .get(0)
                .path("message")
                .path("content")
                .asText();

        return assistantContent; // This is your structured JSON output
    }
}

Creating a Controller to Use the Service

To expose an endpoint that uses the OpenAIService, you can create a simple REST controller.

package com.example.openai;

import org.springframework.web.bind.annotation.*;

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

    private final OpenAIService openAIService;

    public OpenAIController(OpenAIService openAIService) {
        this.openAIService = openAIService;
    }

    @PostMapping("/generate-daily-plan")
    public String generateDailyPlan(@RequestBody UserPreferencesDto preferencesDto) {
        try {
            String result = openAIService.generateDailyPlan(preferencesDto.getPreferences());
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return "{\"error\": \"An error occurred while generating the daily plan.\"}";
        }
    }
}

Create a DTO class to represent the user's preferences:

package com.example.openai;

public class UserPreferencesDto {
    private String preferences;

    public String getPreferences() {
        return preferences;
    }

    public void setPreferences(String preferences) {
        this.preferences = preferences;
    }
}

Explanation of the Code

  • RestTemplate: Used to make HTTP requests to the OpenAI API.
  • ObjectMapper: From Jackson library, used for JSON processing.
  • Building the Request Payload: We create a map representing the JSON payload, including the messages and the JSON schema.
  • Defining the JSON Schema: The schema specifies that the expected output is an object with a schedule array, where each item in the array is an object containing time and activity fields.
  • Including Required Fields: The json_schema object within the response_format now includes the required name and strict fields.
  • Handling the Response: We parse the assistant's response to extract the structured JSON content.

Sample Request and Output

Sample Request

Send a POST request to http://localhost:8080/api/generate-daily-plan with the following JSON body:

{
  "preferences": "I wake up at 6 AM, prefer jogging in the morning, and work from 8 AM to 4 PM."
}

Sample Output

{
   "schedule":[
      {
         "activity":"Wake Up",
         "time":"6:00 AM"
      },
      {
         "activity":"Morning Jog",
         "time":"6:15 AM"
      },
      {
         "activity":"Shower and Get Ready",
         "time":"7:00 AM"
      },
      {
         "activity":"Breakfast",
         "time":"7:30 AM"
      },
      {
         "activity":"Commute to Work",
         "time":"7:45 AM"
      },
      {
         "activity":"Start Work",
         "time":"8:00 AM"
      },
      {
         "activity":"Work",
         "time":"8:00 AM - 12:00 PM"
      },
      {
         "activity":"Lunch Break",
         "time":"12:00 PM"
      },
      {
         "activity":"Work",
         "time":"1:00 PM - 4:00 PM"
      },
      {
         "activity":"Commute Home",
         "time":"4:00 PM"
      },
      {
         "activity":"Relax/Free Time",
         "time":"4:30 PM"
      },
      {
         "activity":"Dinner",
         "time":"6:30 PM"
      },
      {
         "activity":"Evening Activity (Reading/TV/Exercise)",
         "time":"7:00 PM"
      },
      {
         "activity":"Prepare for Next Day",
         "time":"8:30 PM"
      },
      {
         "activity":"Wind Down/Bedtime Routine",
         "time":"9:30 PM"
      },
      {
         "activity":"Sleep",
         "time":"10:00 PM"
      }
   ]
}

Conclusion

By integrating OpenAI's API into your Java Spring Boot applications, you can generate structured outputs that conform to a specified JSON schema. This ensures that the data is reliable, easy to parse, and reduces the need for extensive validation. Including all required fields in your JSON schema, such as the name and strict fields within json_schema, is essential for the API to process your request correctly.

Whether you're creating daily plans, generating reports, or transforming unstructured text into structured data, the OpenAI API provides a robust solution for your Java applications.

Resources