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.) 

Thymeleaf 
  • 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".

  • Click on "Create 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

1. Generate a Spring Boot project using Spring Initializer
2. Add the following dependencies in your pom.xml for required functionalities: 
  • Spring Web: To handle REST endpoints. 
  • Thymeleaf: To render HTML pages.


5. Project Structure


6. Spring Boot Configuration

1. Create a service to interact with Google Gemini.

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

  1. Start the Spring Boot application. 
  2. Access the application via http://localhost:8080
  3. 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

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete