Building an Intelligent Web Application: Integrating Spring Boot, Google Gemini, and Thymeleaf
Integrating Spring Boot, Google Gemini, and Thymeleaf enables developers to create an intelligent web application that uses Google Gemini's AI capabilities for backend processing while using Thymeleaf for rendering dynamic web pages on the frontend.
Here’s a step-by-step explanation:
1. Technology Overview
Spring Boot
- A framework to build RESTful web applications and backend systems.
- Manages the logic, data handling, and integration with third-party services (like Google Gemini).
Google Gemini
Advanced AI models from Google DeepMind, typically accessed via Google Cloud’s Vertex AI API for tasks like:
- Text generation
- Sentiment analysis
- Summarization
- Multimodal inputs (text, image, etc.)
- A server-side template engine for rendering dynamic HTML content.
- Seamlessly integrates with Spring Boot for creating interactive web pages.
2. Use Case Example
Imagine a web app where users can input a prompt, and the app generates AI-powered text or content using Google Gemini. The generated output is displayed dynamically on a webpage rendered using Thymeleaf.
3. Generate an API key for Google's Gemini API
To generate an API key for Google's Gemini API, follow these steps:
- Visit the Google AI Studio and sign in with your Google account.
- After signing in, navigate to the "Solutions" and "Gemini API" section.
- Click on "Get a Gemini API Key".
- Select a project from your existing Google Cloud projects and click on Create API key in existing project.
Copy and securely store both the API key and its corresponding secret, as they won't be retrievable later.
4. Set Up a Spring Boot Application
2. Add the following dependencies in your pom.xml for required functionalities:
- Spring Web: To handle REST endpoints.
- Thymeleaf: To render HTML pages.
package com.knf.dev.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.knf.dev.dto.ContentRequest;
@Service
public class GeminiApiService {
@Value("${gemini.api.key}")
private String apiKey;
public String generateContent(String str) {
String apiUrl = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key="
+ apiKey;
RestTemplate restTemplate = new RestTemplate();
// Build the request body
ContentRequest contentRequest = new ContentRequest();
ContentRequest.Content content = new ContentRequest.Content();
content.setParts(List.of(new ContentRequest.Content.Part(str)));
contentRequest.setContents(List.of(content));
// Set the request headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// Prepare the request entity with body and headers
HttpEntity<ContentRequest> entity = new HttpEntity<>(contentRequest, headers);
// Send POST request to the API
try {
ResponseEntity<String> response = restTemplate.exchange(apiUrl, HttpMethod.POST, entity, String.class);
return response.getBody();
} catch (Exception e) {
e.printStackTrace();
return "Error occurred: " + e.getMessage();
}
}
}
2. application.properties
gemini.api.key=<your_api_key>
7. Create the DTO Classes
1. Part.java
package com.knf.dev.dto;
// Part class representing each item in the "parts" array
public class Part {
private String text;
// Getter and Setter
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
2. UsageMetadata.java
package com.knf.dev.dto;
// UsageMetadata class representing the "usageMetadata" object
public class UsageMetadata {
private int promptTokenCount;
private int candidatesTokenCount;
private int totalTokenCount;
// Getters and Setters
public int getPromptTokenCount() {
return promptTokenCount;
}
public void setPromptTokenCount(int promptTokenCount) {
this.promptTokenCount = promptTokenCount;
}
public int getCandidatesTokenCount() {
return candidatesTokenCount;
}
public void setCandidatesTokenCount(int candidatesTokenCount) {
this.candidatesTokenCount = candidatesTokenCount;
}
public int getTotalTokenCount() {
return totalTokenCount;
}
public void setTotalTokenCount(int totalTokenCount) {
this.totalTokenCount = totalTokenCount;
}
}
3. Candidate.java
package com.knf.dev.dto;
// Candidate class representing each item in the "candidates" array
public class Candidate {
private Content content;
private String finishReason;
private double avgLogprobs;
// Getters and Setters
public Content getContent() {
return content;
}
public void setContent(Content content) {
this.content = content;
}
public String getFinishReason() {
return finishReason;
}
public void setFinishReason(String finishReason) {
this.finishReason = finishReason;
}
public double getAvgLogprobs() {
return avgLogprobs;
}
public void setAvgLogprobs(double avgLogprobs) {
this.avgLogprobs = avgLogprobs;
}
}
4. Content.java
package com.knf.dev.dto;
import java.util.List;
// Content class representing the "content" object
public class Content {
private List<Part> parts;
private String role;
// Getters and Setters
public List<Part> getParts() {
return parts;
}
public void setParts(List<Part> parts) {
this.parts = parts;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@Override
public String toString() {
return "Content [parts=" + parts + ", role=" + role + "]";
}
}
5. ContentRequest.java
package com.knf.dev.dto;
import java.util.List;
public class ContentRequest {
private List<Content> contents;
public static class Content {
private List<Part> parts;
public static class Part {
private String text;
public Part(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
public List<Part> getParts() {
return parts;
}
public void setParts(List<Part> parts) {
this.parts = parts;
}
}
public List<Content> getContents() {
return contents;
}
public void setContents(List<Content> contents) {
this.contents = contents;
}
}
6. ApiResponse.java
package com.knf.dev.dto;
import java.util.List;
// Root class representing the entire JSON response
public class ApiResponse {
private List<Candidate> candidates;
private UsageMetadata usageMetadata;
private String modelVersion;
// Getters and Setters
public List<Candidate> getCandidates() {
return candidates;
}
public void setCandidates(List<Candidate> candidates) {
this.candidates = candidates;
}
public UsageMetadata getUsageMetadata() {
return usageMetadata;
}
public void setUsageMetadata(UsageMetadata usageMetadata) {
this.usageMetadata = usageMetadata;
}
public String getModelVersion() {
return modelVersion;
}
public void setModelVersion(String modelVersion) {
this.modelVersion = modelVersion;
}
}
8. Create the Controller
package com.knf.dev.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.knf.dev.dto.ApiResponse;
import com.knf.dev.dto.Candidate;
import com.knf.dev.service.GeminiApiService;
@Controller
public class HomeController {
@Autowired
private GeminiApiService geminiApiService;
@Autowired
private ObjectMapper objectMapper;
// Display the form
@GetMapping("/")
public String showForm() {
return "index";
}
// Handle the form submission and generate the response
@PostMapping("/submit")
public String handleFormSubmission(@RequestParam("userInput") String userInput, Model model)
throws JsonMappingException, JsonProcessingException {
String op = geminiApiService.generateContent(userInput);
ApiResponse response = objectMapper.readValue(op, ApiResponse.class);
List<Candidate> c = response.getCandidates();
String output = c.get(0).getContent().getParts().get(0).getText();
// Add result to the model
model.addAttribute("result", output);
// Return the same form with the result to be displayed as a paragraph
return "index";
}
}
9. Create Thymeleaf Frontend
- Input Page (index.html): A simple form to take user input.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ask Me</title>
<!-- Include Bootstrap CSS -->
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1>Ask Me</h1>
<!-- Form to submit text input -->
<form action="/submit" method="post">
<div class="mb-3">
<label for="userInput" class="form-label">Your Query:</label> <input
type="text" class="form-control" id="userInput" name="userInput"
required>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<br>
<form action="/" method="get">
<button type="submit" class="btn btn-danger">Clear</button>
</form>
<br>
<!-- Display the result as a paragraph if available -->
<figure class="text-center">
<blockquote class="blockquote">
<div th:if="${result != null}" class="text-bg-success p-3"><p th:text="${result}"></p></div>
</blockquote>
</figure>
</div>
<!-- Include Bootstrap JS -->
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js">
</script>
</body>
</html>
10. Run the Application
- Start the Spring Boot application.
- Access the application via http://localhost:8080.
- Input a prompt, and the Google Gemini response will be displayed dynamically on the results page.
11. Download Source Code
git clone https://github.com/knowledgefactory4u/springboot-google-gemini-integration.git