Complete Guide to Building a Spring AI + Ollama Embedding Vectorization Project

This guide walks through adding embedding support to a Spring AI application by configuring Ollama, creating an EmbeddingService for vector generation and similarity calculations, exposing REST endpoints via EmbeddingController, and providing a simple HTML front‑end for interactive testing, with step‑by‑step instructions and code samples.

The Dominant Programmer
The Dominant Programmer
The Dominant Programmer
Complete Guide to Building a Spring AI + Ollama Embedding Vectorization Project

Overview

The article shows how to extend a Spring Boot + LangChain4j + Ollama RAG project with text‑embedding (vectorization) capabilities, enabling private document search and similarity analysis.

1. Application configuration

server:
  port: 885
logging:
  level:
    com.badao: debug
    org.springframework.ai: debug
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: qwen2.5
          temperature: 0.7
      embedding:
        options:
          model: nomic-embed-text

Before running the service, pull the embedding model in Ollama (e.g., ollama pull nomic-embed-text) or replace it with another model such as bge-m3 or mxbai-embed-large.

2. EmbeddingService

package com.badao.ai.service;

import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.stereotype.Service;
import java.util.*;

@Service
public class EmbeddingService {
    private final EmbeddingModel embeddingModel;
    public EmbeddingService(EmbeddingModel embeddingModel) { this.embeddingModel = embeddingModel; }

    /** Convert a single text to a float vector */
    public float[] embed(String text) { return embeddingModel.embed(text); }

    /** Batch conversion returning the full response */
    public EmbeddingResponse embedForResponse(List<String> texts) { return embeddingModel.embedForResponse(texts); }

    /** Cosine similarity between two texts */
    public double cosineSimilarity(String text1, String text2) {
        float[] vec1 = embed(text1);
        float[] vec2 = embed(text2);
        return cosineSimilarity(vec1, vec2);
    }

    /** Cosine similarity between two vectors */
    public static double cosineSimilarity(float[] vec1, float[] vec2) {
        double dot = 0.0, norm1 = 0.0, norm2 = 0.0;
        for (int i = 0; i < vec1.length; i++) {
            dot += vec1[i] * vec2[i];
            norm1 += vec1[i] * vec1[i];
            norm2 += vec2[i] * vec2[i];
        }
        if (norm1 == 0 || norm2 == 0) return 0.0;
        return dot / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }

    /** Batch similarity matrix */
    public Map<String, Object> batchSimilarity(List<String> texts) {
        int n = texts.size();
        List<float[]> vectors = new ArrayList<>();
        for (String t : texts) vectors.add(embed(t));
        List<List<Double>> matrix = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            List<Double> row = new ArrayList<>();
            for (int j = 0; j < n; j++) {
                row.add(i == j ? 1.0 : cosineSimilarity(vectors.get(i), vectors.get(j)));
            }
            matrix.add(row);
        }
        return Map.of(
            "texts", texts,
            "dimension", vectors.get(0).length,
            "similarityMatrix", matrix
        );
    }
}

3. EmbeddingController

package com.badao.ai.controller;

import com.badao.ai.service.EmbeddingService;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.web.bind.annotation.*;
import java.util.*;

@RestController
public class EmbeddingController {
    private final EmbeddingService embeddingService;
    public EmbeddingController(EmbeddingService embeddingService) { this.embeddingService = embeddingService; }

    @GetMapping("/ai/embed")
    public Map<String, Object> embed(@RequestParam(value = "text", defaultValue = "Hello World") String text) {
        float[] vector = embeddingService.embed(text);
        return Map.of(
            "text", text,
            "dimension", vector.length,
            "vectorPreview", previewVector(vector, 10)
        );
    }

    @PostMapping("/ai/embed/similarity")
    public Map<String, Object> similarity(@RequestBody Map<String, String> req) {
        String t1 = req.getOrDefault("text1", "");
        String t2 = req.getOrDefault("text2", "");
        float[] v1 = embeddingService.embed(t1);
        float[] v2 = embeddingService.embed(t2);
        double sim = EmbeddingService.cosineSimilarity(v1, v2);
        return Map.of(
            "text1", t1,
            "text2", t2,
            "similarity", Math.round(sim * 10000.0) / 100.0,
            "dimension", v1.length
        );
    }

    @PostMapping("/ai/embed/batch-similarity")
    public Map<String, Object> batchSimilarity(@RequestBody Map<String, Object> req) {
        @SuppressWarnings("unchecked")
        List<String> texts = (List<String>) req.get("texts");
        if (texts == null || texts.isEmpty()) texts = List.of("苹果", "香蕉", "电脑", "笔记本");
        return embeddingService.batchSimilarity(texts);
    }

    @PostMapping("/ai/embed/batch")
    public Map<String, Object> batchEmbed(@RequestBody Map<String, Object> req) {
        @SuppressWarnings("unchecked")
        List<String> texts = (List<String>) req.get("texts");
        if (texts == null || texts.isEmpty()) texts = List.of("Hello", "World");
        EmbeddingResponse resp = embeddingService.embedForResponse(texts);
        List<Map<String, Object>> results = resp.getResults().stream()
            .map(r -> Map.of(
                "index", r.getIndex(),
                "dimension", r.getOutput().size(),
                "vectorPreview", previewVector(toFloatArray(r.getOutput()), 10)
            ))
            .toList();
        return Map.of(
            "texts", texts,
            "model", "ollama-nomic-embed-text",
            "results", results
        );
    }

    private static List<Float> previewVector(float[] vector, int max) {
        int limit = Math.min(vector.length, max);
        List<Float> preview = new ArrayList<>();
        for (int i = 0; i < limit; i++) preview.add(Math.round(vector[i] * 10000f) / 10000f);
        return preview;
    }

    private static float[] toFloatArray(List<Double> list) {
        float[] arr = new float[list.size()];
        for (int i = 0; i < list.size(); i++) arr[i] = list.get(i).floatValue();
        return arr;
    }
}

4. Front‑end test page (embedding-test.html)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Embedding Test</title>
  <style>/* minimal styling omitted for brevity */</style>
</head>
<body>
  <div class="container">
    <h1>🔢 Text Embedding</h1>
    <!-- Single text embedding UI -->
    <div class="card">
      <h2>📌 1. Single Text</h2>
      <textarea id="singleText" placeholder="Enter text...">人工智能正在改变世界</textarea>
      <button onclick="doEmbed()">🔍 Embed</button>
      <div class="result-box" id="singleResult"><span class="loading">Waiting…</span></div>
    </div>
    <!-- Pair similarity UI -->
    <div class="card">
      <h2>📊 2. Pair Similarity</h2>
      <textarea id="textA" placeholder="Text A">苹果是一种水果</textarea>
      <textarea id="textB" placeholder="Text B">香蕉也是一种水果</textarea>
      <button onclick="doSimilarity()">📐 Compute</button>
      <div class="result-box" id="similarityResult"><span class="loading">Waiting…</span></div>
    </div>
    <!-- Batch matrix UI -->
    <div class="card">
      <h2>🧮 3. Batch Matrix</h2>
      <textarea id="batchText" placeholder="One line per text">苹果
香蕉
电脑
手机</textarea>
      <button onclick="doBatchSimilarity()">🧮 Generate</button>
      <div class="result-box" id="batchResult"><span class="loading">Waiting…</span></div>
    </div>
  </div>
  <script>
    // helper functions omitted for brevity – they set example texts, call the REST APIs and render results
  </script>
</body>
</html>

5. Usage steps

Pull the embedding model : ollama pull nomic-embed-text Start the Spring Boot application : run SpringAiDemoApplication or the provided 一键启动.bat Open the test page : http://localhost:885/embedding-test.html Existing functionality (e.g., function-call-test.html) remains unchanged

6. API reference

GET /ai/embed?text=xxx

– single‑text vectorization POST /ai/embed/similarity – cosine similarity of two texts POST /ai/embed/batch-similarity – similarity matrix for a list of texts POST /ai/embed/batch – batch vectorization returning full responses

7. Maven pom (unchanged)

<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.5</version>
  </parent>
  <groupId>com.badao.ai</groupId>
  <artifactId>spring-ai-ollama-functioncall</artifactId>
  <version>1.0</version>
  <properties>
    <java.version>17</java.version>
    <spring-ai.version>1.0.0-M6</spring-ai.version>
    <spring-ai-alibaba.version>1.0.0-M6.1</spring-ai-alibaba.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
      <version>${spring-ai-alibaba.version}</version>
    </dependency>
  </dependencies>
  <repositories>
    <repository>
      <id>spring-milestones</id>
      <url>https://repo.spring.io/milestone</url>
    </repository>
  </repositories>
</project>

8. Test result

Embedding test screenshot
Embedding test screenshot
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaEmbeddingREST APISpring AIVectorizationOllama
The Dominant Programmer
Written by

The Dominant Programmer

Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.