{"id":55057,"date":"2026-03-12T11:45:11","date_gmt":"2026-03-12T18:45:11","guid":{"rendered":"https:\/\/griddb.net\/?p=55057"},"modified":"2026-03-12T11:45:11","modified_gmt":"2026-03-12T18:45:11","slug":"building-a-smarter-book-inventory-using-vaadin-and-spring-ai","status":"publish","type":"post","link":"https:\/\/www.griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/","title":{"rendered":"Building a Smarter Book Inventory using Vaadin and Spring AI"},"content":{"rendered":"<p>Here is a project showcasing a simple yet powerful project: a smarter website for managing book collections.<\/p>\n<p>In this post, we&#8217;re going to build this web application from the ground up. We will use Spring Boot, Spring AI, <a href=\"https:\/\/vaadin.com\/docs\/latest\/flow\/what-is-flow\">Vaadin Flow<\/a> to create it. And we will store the data in the cloud using GridDB Cloud.<\/p>\n<h2 id=\"core-features\">Core Features<\/h2>\n<p>So, what exactly are we building? Let&#8217;s break it down.<\/p>\n<ol>\n<li>Importing Books from CSV<\/li>\n<\/ol>\n<p>    A common and easy way to get a list of existing books is often from a CSV file. Think of a simple spreadsheet with columns like `Title` and `Author`.<br \/>\n    We will build a small part of the application to read this CSV file. This `parser` will read lines, split them by commas, and create `Book` object with `title`, `authors`, `publisher`, and `rating`. This will serve as our initial dataset in the database. It&#8217;s the first step before adding more exciting features.<\/p>\n<ol>\n<li>AI Enrichment<\/li>\n<\/ol>\n<p>    Once we have the initial dataset, we&#8217;ll take each book record and look up it&#8217;s genre and a brief summary. We&#8217;ll use Spring AI to send request to the Open AI API. Then, we&#8217;ll read the response, extract the genre and summary, and update the book data.<\/p>\n<p>Based on the above features we need the following components:<\/p>\n<ul>\n<li>A web interface for listing books, upload the CSV, and book detail page where user ask the AI to update book&#8217;s genre and summary by clicking a button. We will develop this UI using Vaadin Flow.<\/li>\n<\/ul>\n<ul>\n<li>CSV parser component<\/li>\n<\/ul>\n<ul>\n<li>An AI Enrichment Service that uses Spring AI to interact with LLM model.<\/li>\n<\/ul>\n<ul>\n<li>A NoSQL Database Service to interact with GridDB Cloud API. We will use Spring `RestClient` to handle the `request` and `response`. This will also convert Java `record` into `HTTP` body according to the API specifications.<\/li>\n<\/ul>\n<h2 id=\"tech-stack\">Tech Stack<\/h2>\n<p>For this project, I selected:<\/p>\n<ul>\n<li><a href=\"https:\/\/spring.io\/projects\/spring-boot\">Spring Boot<\/a><\/li>\n<\/ul>\n<p>    Spring Boot streamlines development with auto-configuration, an embedded server, and starter dependencies. This effortless setup accelerates our ability to build and launch applications swiftly.<\/p>\n<ul>\n<li><a href=\"https:\/\/vaadin.com\/docs\/latest\/flow\/what-is-flow\">Vaadin Flow<\/a><\/li>\n<\/ul>\n<p>    Vaadin Flow serves as a versatile Java UI framework, allowing us to construct web applications purely in Java. This approach simplifies our process by minimizing the hassle of juggling separate frontends. Packed with a treasure trove of pre-built components, Vaadin is tailor-made for data-rich business applications, ensuring users enjoy a seamless experience.<\/p>\n<ul>\n<li><a href=\"https:\/\/spring.io\/projects\/spring-ai\">Spring AI<\/a><\/li>\n<\/ul>\n<p>    Spring AI is a powerful extension of the <a href=\"https:\/\/spring.io\/projects\/spring-framework\">Spring Framework<\/a>. It empowers Java developers to craft AI-driven applications with minimal reskilling required. By tapping into the strengths of the Spring Framework, Spring AI opens the door to advanced AI features, simplifying the journey to creating intelligent apps.<\/p>\n<ul>\n<li><a href=\"https:\/\/www.global.toshiba\/ww\/products-solutions\/ai-iot\/griddb\/product\/griddb-cloud.html\">GridDB Cloud<\/a><\/li>\n<\/ul>\n<p>    GridDB Cloud is a fully managed, cloud-based database offered by GridDB, designed to store and process massive volumes of time-series data in real-time. Read this <a href=\"https:\/\/griddb.net\/en\/blog\/griddb-cloud-quick-start-guide\/\">quick start guide<\/a> to learn how to use GridDB Cloud. You can sign up for a GridDB Cloud Free instance at this link: https:\/\/form.ict-toshiba.jp\/download<em>form<\/em>griddb<em>cloud<\/em>freeplan_e.<\/p>\n<h2 id=\"create-a-new-vaadin-project\">Create a new Vaadin Project<\/h2>\n<p>To create a Vaadin Flow project, go to <a href=\"https:\/\/start.vaadin.com\/app\">start.vaadin.com<\/a>. This starter project has a basic application with a fully-functional end-to-end workflow. Choose a pure Java option with Vaadin Flow. Click the download button, then unzip and open the project into your favorite IDE.<\/p>\n<p>You should now see a typical Maven project as shown below:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ \u251c\u2500\u2500\u2500.mvn\n\n$ \u251c\u2500\u2500 mvnw\n$ \u251c\u2500\u2500 mvnw.cmd\n$ \u251c\u2500\u2500 pom.xml\n$ \u251c\u2500\u2500 README.md\n$ \u251c\u2500\u2500\u2500src\n$ \u2502   \u251c\u2500\u2500\u2500main\n$ \u2502   \u2502   \u251c\u2500\u2500\u2500frontend\n$ \u2502   \u2502   \u251c\u2500\u2500\u2500java\n$ \u2502   \u2502   \u2514\u2500\u2500\u2500resources\n$ \u2502   \u2514\u2500\u2500\u2500test\n$ \u2502       \u251c\u2500\u2500\u2500java\n$ \u2502       \u2514\u2500\u2500\u2500resources<\/code><\/pre>\n<\/div>\n<h2 id=\"data-access\">Data Access<\/h2>\n<p>Next, we need a domain object to hold the Book data as follow:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">public record Book(String id, String title, String authors, String publisher, Double rating, String genres,\n\n        String summary, Long goodreadsBookId, String goodreadsUrl) {\n    public Book(String id, String title, String authors, String publisher, Double rating, String genres, String summary,\n            Long goodreadsBookId) {\n        this(id, title, authors, publisher, rating, genres, summary, goodreadsBookId, null);\n    }\n}<\/code><\/pre>\n<\/div>\n<p>Next, we create a `BookService.java` class to fetch and store data. Also to pass data to the presentation layer.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Service\n\npublic class BookService {\n    private final BookContainer bookContainer;\n\n\n\n    public BookService(BookContainer bookContainer) {\n        this.bookContainer = bookContainer;\n    }\n\n\n\n    public List<Book> listBooks() {\n        return this.bookContainer.getBooks();\n    }\n\n\n\n    public Book getBook(String id) {\n        Book book = this.bookContainer.getBook(id);\n        if (book == null) {\n            throw new IllegalArgumentException(\"Book with ID \" + id + \" does not exist.\");\n        }\n        return book;\n    }\n\n\n\n    public void createTableBooks() {\n        this.bookContainer.createTableBooks();\n    }\n\n\n\n    public void saveBooks(List<Book> books) {\n        if (books == null || books.isEmpty()) {\n            return;\n        }\n        List<Book> newBooks = books.stream().map(book -> {\n            String id = (book.id() != null) ? book.id() : nextId();\n            return new Book(id, book.title(), book.authors(), book.publisher(), book.rating(), book.genres(),\n                    book.summary(), book.goodreadsBookId(), book.goodreadsUrl());\n        }).collect(Collectors.toList());\n        this.bookContainer.saveBooks(newBooks);\n    }\n\n\n\n    public static String nextId() {\n        return \"book_\" + TsidCreator.getTsid().format(\"%S\");\n    }\n}<\/code><\/pre>\n<\/div>\n<ul>\n<li>The `saveBooks()` method accept a list of books. Both creating a new book and updating a book will use this method.<\/li>\n<li>We generate the book&#8217;s ID using <a href=\"http:\/\/github.com\/f4b6a3\/tsid-creator\">Time-Sorted Unique Identifiers (TSID)<\/a>.<\/li>\n<\/ul>\n<p>Next, we create the `BookContainer.java` to interact with the database API:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Service\n\npublic class BookContainer {\n    private final GridDbCloudClient gridDbCloudClient;\n    private static final String BOOKS<em>TBL<\/em>NAME = \"Books\";\n    public BookContainer(GridDbCloudClient gridDbCloudClient) {\n        this.gridDbCloudClient = gridDbCloudClient;\n    }\n    public void createTableBooks() {\n        List<GridDbColumn> columns = List.of(new GridDbColumn(\"id\", \"STRING\", Set.of(\"TREE\")),\n                new GridDbColumn(\"title\", \"STRING\"), new GridDbColumn(\"authors\", \"STRING\"),\n                new GridDbColumn(\"publisher\", \"STRING\"), new GridDbColumn(\"rating\", \"DOUBLE\"),\n                new GridDbColumn(\"genres\", \"STRING\"), new GridDbColumn(\"summary\", \"STRING\"),\n                new GridDbColumn(\"goodreadsBookId\", \"LONG\"), new GridDbColumn(\"goodreadsUrl\", \"STRING\"));\n\n\n\n        GridDbContainerDefinition containerDefinition = GridDbContainerDefinition.createContainer(BOOKS<em>TBL<\/em>NAME,\n                columns);\n        this.gridDbCloudClient.createContainer(containerDefinition);\n    }\n}<\/code><\/pre>\n<\/div>\n<ul>\n<li>`GridDbCloudClient`: to communicate with the GridDB Cloud database. Provided automatically by Spring Boot through constructor-based dependency injection. <\/li>\n<li>`BOOKS<em>TBL<\/em>NAME`: table name as a constant.<\/li>\n<li>`createTableBooks()`: create a `Books` table (`collection`) in GridDB Cloud database to hold records for our books. This method started by creating a `List` of `GridDBColumn` objects. Each object describes one column with data type. Then create the container definition and use the `GridDbCloudClient` to actually create the table.<\/li>\n<\/ul>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">public class GridDbCloudClient {\n\n    private final RestClient restClient;\n    public GridDbCloudClient(String baseUrl, String authToken) {\n        this.restClient = RestClient.builder().baseUrl(baseUrl).defaultHeader(\"Authorization\", \"Basic \" + authToken)\n                .defaultHeader(\"Content-Type\", \"application\/json\").defaultHeader(\"Accept\", \"application\/json\")\n                .build();\n    }\n    public void createContainer(GridDbContainerDefinition containerDefinition) {\n        try {\n            restClient.post().uri(\"\/containers\").body(containerDefinition).retrieve().toBodilessEntity();\n        } catch (Exception e) {\n            throw new GridDbException(\"Failed to create container\", HttpStatusCode.valueOf(500), e.getMessage(), e);\n        }\n    }\n}<\/code><\/pre>\n<\/div>\n<p>The `GridDbCloudClient` class is a Java client for interacting with the GridDB Cloud Web API. It provides methods for creating containers, registering rows, acquiring rows, and executing POST requests. We set `RestClient` up with a base URL and a basic authorization header so that it could connect to the GridDB Cloud Web API and authenticate.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">\/\/BookContainer.java\n\n    public void saveBooks(List<Book> books) {\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"[\");\n        for (int i = 0; i < books.size(); i++) {\n            Book book = books.get(i);\n            sb.append(\"[\");\n            sb.append(\"\\\"\").append(book.id()).append(\"\\\"\");\n            sb.append(\", \");\n            sb.append(\"\\\"\").append(book.title()).append(\"\\\"\");\n            sb.append(\", \");\n            sb.append(\"\\\"\").append(book.authors()).append(\"\\\"\");\n            sb.append(\", \");\n            sb.append(\"\\\"\").append(book.publisher()).append(\"\\\"\");\n            sb.append(\", \");\n            sb.append(book.rating());\n            sb.append(\", \");\n            sb.append(\"\\\"\").append(book.genres() != null ? book.genres() : \"\").append(\"\\\"\");\n            sb.append(\", \");\n            sb.append(\"\\\"\").append(book.summary() != null ? book.summary() : \"\").append(\"\\\"\");\n            sb.append(\", \");\n            sb.append(book.goodreadsBookId());\n            sb.append(\", \");\n            sb.append(\"\\\"\").append(book.goodreadsUrl() != null ? book.goodreadsUrl() : \"\").append(\"\\\"\");\n            sb.append(\"]\");\n            if (i < books.size() - 1) {\n                sb.append(\", \");\n            }\n        }\n        sb.append(\"]\");\n        String result = sb.toString();\n        this.gridDbCloudClient.registerRows(BOOKS<em>TBL<\/em>NAME, result);\n    }<\/code><\/pre>\n<\/div>\n<ul>\n<li>`saveBooks(List<Book> books)`: designed to receieve a List of Book objects and save them into the `Book` collection created earlier. First, format the list into a specialized string representation of an array of arrays, and then uses the GridDB client to send this formatted string to save all the book records in the database.<\/li>\n<\/ul>\n<p>An illustration of the request body used to register rows into a container:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-json\">[\n\n    [\"abf8e412\", \"The Ultimate Hitchhiker's Guide to the Galaxy\", \"Douglas Adams\", \"Del Rey Books\", 4.37, \"\", \"\", 13],\n    [\"5f8bdef1\", \"The Lost Continent: Travels in Small Town America\", \"Bill Bryson\", \"William Morrow Paperbacks\", 3.83, \"\", \"\", 26]\n]<\/code><\/pre>\n<\/div>\n<p>Getting books from GridDB Cloud Cloud.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">\/\/BookContainer.java\n\n    public List<Book> getBooks() {\n        AcquireRowsRequest requestBody = AcquireRowsRequest.builder().limit(50L).build();\n        AcquireRowsResponse response = this.gridDbCloudClient.acquireRows(BOOKS<em>TBL<\/em>NAME, requestBody);\n        if (response == null || response.getRows() == null) {\n            return List.of();\n        }\n        List<Book> books = convertResponseToBook(response);\n        return books;\n    }<\/code><\/pre>\n<\/div>\n<ul>\n<li>Build the Request body by creating a `AcquireRowsRequest` object, tells GridDB how many rows I want to get.<\/li>\n<li>Use the `gridDbCloudClient.acquireRows(BOOKS<em>TBL<\/em>NAME, requestBody)` to send request to the API.<\/li>\n<li>Check if the response is null or if there are no rows in the response.<\/li>\n<li>If everything is okay, convert the database response into a list of `Book` object and return it.<\/li>\n<\/ul>\n<p>Converting raw data to `Book` objects.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">\/\/BookContainer.java\n\n    private List<Book> convertResponseToBook(AcquireRowsResponse response) {\n        List<Book> books = response.getRows().stream().map(row -> {\n            try {\n                var book = new Book(row.get(0).toString(), row.get(1).toString(), row.get(2).toString(),\n                        row.get(3).toString(),\n                        Optional.ofNullable(row.get(4)).map(Object::toString).map(Double::valueOf).orElse(null),\n                        Optional.ofNullable(row.get(5)).map(Object::toString).orElse(null),\n                        Optional.ofNullable(row.get(6)).map(Object::toString).orElse(null),\n                        Optional.ofNullable(row.get(7)).map(Object::toString).map(Long::valueOf).orElse(null),\n                        Optional.ofNullable(row.get(8)).map(Object::toString).orElse(null));\n                return book;\n            } catch (Exception e) {\n                return null;\n            }\n        }).filter(book -> book != null).toList();\n        return books;\n    }<\/code><\/pre>\n<\/div>\n<ul>\n<li>Using Java Streams to process each row from the database.<\/li>\n<li>For each row, create a new `Book` object. The database returns data as a list where each position represents a different field.<\/li>\n<li>Check for null values and handle errors gracefully.<\/li>\n<\/ul>\n<h2 id=\"csv-parser-for-goodreads-book\">CSV Parser for Goodreads Book<\/h2>\n<p>In this project we are going to use <a href=\"https:\/\/www.kaggle.com\/datasets\/bahramjannesarr\/goodreads-book-datasets-10m\">Goodreads book dataset<\/a>. Goodreads is the world&#8217;s largest site for readers and book recommendations.<\/p>\n<p>The book sample in CSV:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-csv\">Id,Name,RatingDist1,pagesNumber,RatingDist4,RatingDistTotal,PublishMonth,PublishDay,Publisher,CountsOfReview,PublishYear,Language,Authors,Rating,RatingDist2,RatingDist5,ISBN,RatingDist3\n\n1339,\"Loving and Dying: A Reading of Plato's Phaedo, Symposium, and Phaedrus\",1:0,288,4:2,total:5,12,11,University Press of America,0,2001,,Richard Gotshalk,4.6,2:0,5:3,0761820728,3:0<\/code><\/pre>\n<\/div>\n<p>Why creating a custom Parser?<br \/>\n> Because this CSV data contain commas within the actual data.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">\/\/GoodReadBookCSVParser.java\n\ntry (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {\n    String line;\n    boolean isFirst = true;\n    while ((line = reader.readLine()) != null) {\n        if (isFirst) {\n            isFirst = false;\n            continue;\n        }\n        String[] fields = parseCsvLine(line);\n        \/\/assign parser result into each field\n        Book book = new Book(null, title, authors, publisher, rating, null, null, goodreadsBookId);\n        books.add(book);\n    }\n}<\/code><\/pre>\n<\/div>\n<ul>\n<li>The parser receive `InputStream` and wrap it into `BufferedReader`, buffering characters to efficiently read characters, arrays, and lines. We use a `Try-with-resource` block to ensure the File resource gets closed properly, even if something goes wrong. Also specify UTF-8 encoding to handle special characters correctly.<\/li>\n<\/ul>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">\/\/GoodReadBookCSVParser.java\n\nprivate String[] parseCsvLine(String line) {\n    List<String> result = new ArrayList<>();\n    boolean inQuotes = false;\n    StringBuilder sb = new StringBuilder();\n    for (int i = 0; i < line.length(); i++) {\n        char c = line.charAt(i);\n        if (c == '\"') {\n            inQuotes = !inQuotes;\n        } else if (c == ',' &#038;&#038; !inQuotes) {\n            result.add(sb.toString());\n            sb.setLength(0);\n        } else {\n            sb.append(c);\n        }\n    }\n    result.add(sb.toString());\n    return result.toArray(new String[0]);\n}<\/code><\/pre>\n<\/div>\n<ul>\n<li>`parseCsvLine()`: goes through each character one by one. Tracking whether currently inside a quoted field. When there is a comma, only treat it as a field separator if NOT inside quotes.<\/li>\n<\/ul>\n<h2 id=\"creating-vaadin-views-and-layouts\">Creating Vaadin views and layouts<\/h2>\n<p>Because this project was generated using <a href=\"https:\/\/start.vaadin.com\/\">Vaadin Start<\/a>, we got a fully functional application that can be easily extended and customized. So, we will add new view by following the existing structure.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ com.company\n\n$ \u251c\u2500\u2500\u2500base\n$ \u2502   \u2514\u2500\u2500\u2500ui\n$ \u2502       \u251c\u2500\u2500\u2500component\n$ \u2502       \u2514\u2500\u2500\u2500view\n$ \u2514\u2500\u2500\u2500bookinventory\n$     \u251c\u2500\u2500\u2500domain\n$     \u251c\u2500\u2500\u2500seeder\n$     \u251c\u2500\u2500\u2500service\n$     \u2514\u2500\u2500\u2500ui\n$         \u2514\u2500\u2500\u2500view\n$                 BookDetailView.java\n$                 BookListView.java<\/code><\/pre>\n<\/div>\n<p>Now, let's add `BookListView` component to display all books.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">\/\/BookListView.java\n\n@Route(\"book-list\")\n@PageTitle(\"Book List\")\n@Menu(order = 0, icon = \"vaadin:book\", title = \"Book List\")\npublic class BookListView extends Main {\n    private final Logger log = LoggerFactory.getLogger(getClass());\n    private final BookService bookService;\n    private final Grid<Book> bookGrid;\n}<\/code><\/pre>\n<\/div>\n<ul>\n<li>`@Route(\"book-list\")` : defined a Flow view that can be accessed at `htt:\/\/localhost:8080\/book-list`.<\/li>\n<li>`@Menu`: make a Flow view appear in the menu.<\/li>\n<li>Use `BookService` to get list of books and to save uploaded books from CSV<\/li>\n<li>Vaadin `Grid` to display tabular data of books.<\/li>\n<li>A component for uploading a CSV file as shown at <a href=\"https:\/\/github.com\/alifruliarso\/book-inventory-withspringai\/blob\/362d98f7f7dec7a94f34684115916d4ea8f0e99a\/src\/main\/java\/com\/galapea\/techblog\/bookinventory\/ui\/view\/BookListView.java#L106\">BookListView.java<\/a><\/li>\n<\/ul>\n<p><a href=\"\/wp-content\/uploads\/2026\/03\/book-list.png\"><img fetchpriority=\"high\" decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/03\/book-list.png\" alt=\"\" width=\"1351\" height=\"397\" class=\"aligncenter size-full wp-image-55059\" srcset=\"\/wp-content\/uploads\/2026\/03\/book-list.png 1351w, \/wp-content\/uploads\/2026\/03\/book-list-300x88.png 300w, \/wp-content\/uploads\/2026\/03\/book-list-1024x301.png 1024w, \/wp-content\/uploads\/2026\/03\/book-list-768x226.png 768w, \/wp-content\/uploads\/2026\/03\/book-list-600x176.png 600w\" sizes=\"(max-width: 1351px) 100vw, 1351px\" \/><\/a><\/p>\n<p>Next, adding `BookDetailView`. From the book list, we click one of the book titles, then navigate to the book detail.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Route(\"book-detail\")\n\n@PageTitle(\"Book Detail\")\npublic class BookDetailView extends VerticalLayout implements HasUrlParameter<String> {\n    private final BookService bookService;\n    private FormLayout content;\n    private String bookId;\n\n\n\n    private Button fetchGenreBtn;\n    private ProgressBar progressBar;\n    private NativeLabel progresLabel;\n    private Button fetchSummaryBtn;\n\n\n\n    public BookDetailView(BookService bookService) {\n        this.bookService = bookService;\n        fetchGenreBtn.addClickListener(e -> {\n            var ui = UI.getCurrent();\n            Book book = getCurrentBook();\n            progresLabel.setVisible(true);\n            progresLabel.setText(\"Asking AI for \" + book.title() + \"...\");\n            progressBar.setVisible(true);\n            progressBar.setIndeterminate(true);\n            bookService.asyncGenerateGenre(book.id(), ui.accessLater(this::onJobCompleted, null),\n                    ui.accessLater(progressBar::setValue, null), ui.accessLater(this::onJobFailed, null));\n        });\n    }\n}<\/code><\/pre>\n<\/div>\n<ul>\n<li>Extends `VerticalLayout`, so all components added will be shown vertically.<\/li>\n<li>Implements `HasUrlParameter<String>`, so the page can receive a book ID from the URL.<\/li>\n<li>We use `BookService` to fetch the book from database, call the  AI assistant, and update the book data.<\/li>\n<li>The `content` form layout will show book details.<\/li>\n<li>When the `fetchGenreBtn` button clicked, we show a progress bar while the `bookService` starting the AI task.<\/li>\n<\/ul>\n<p><a href=\"\/wp-content\/uploads\/2026\/03\/book-detail.png\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/03\/book-detail.png\" alt=\"\" width=\"606\" height=\"592\" class=\"aligncenter size-full wp-image-55058\" srcset=\"\/wp-content\/uploads\/2026\/03\/book-detail.png 606w, \/wp-content\/uploads\/2026\/03\/book-detail-300x293.png 300w, \/wp-content\/uploads\/2026\/03\/book-detail-600x586.png 600w\" sizes=\"(max-width: 606px) 100vw, 606px\" \/><\/a><\/p>\n<h2 id=\"spring-ai-integration\">Spring AI Integration<\/h2>\n<p>Our book data is ready in the database and we can access it through the listing and details page. Now it is time to enhance the functionality by integrating AI. We're going to dive into how we can use AI to automatically figure out the genre and write a concise summary for each book.<\/p>\n<p>Before we dive into the \"how\", let's talk about an important part: <strong>Large Language Models (LLM)<\/strong>. These are powerful AI systems (like OpenAI's GPT models) that can understand and create human-like text. They let us \"ask\" for a book's genre or summary.<\/p>\n<p>Now, we need to connect to these AI models. This is where <strong>Spring AI<\/strong> comes in as our best friend. Spring AI simplifies how we work with LLMs. We don't have to send raw HTTP requests to OpenaI. Spring AI handles all that heavy lifting. It provides a consistent way to interact with different LLM provider. So, whether we're using OpenAI today or decide to switch to Google Gemini tomorrow, our code for making AI calls remains largerly the same.<\/p>\n<p>Include Spring AI dependency in our `pom.xml`:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-xml\">    <properties>\n        <spring-ai.version>1.0.0-SNAPSHOT<\/spring-ai.version>\n    <\/properties>\n    <dependency>\n        <groupId>org.springframework.ai<\/groupId>\n        <artifactId>spring-ai-starter-model-openai<\/artifactId>\n        <version>${spring-ai.version}<\/version>\n    <\/dependency><\/code><\/pre>\n<\/div>\n<p>Configure API Key:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-properties\"># The OpenAI API key to use\n\nspring.ai.openai.api-key=${OPENAI<em>API<\/em>KEY}\n\n<h1 id=\"the-default-openai-model-to-use\">The default OpenAI model to use<\/h1>\n\nspring.ai.openai.model=${OPENAI_MODEL: gpt-4o-mini}<\/code><\/pre>\n<\/div>\n<p>Configure `ChatClient`:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Configuration\n\npublic class ChatClientConfig {\n    @Bean\n    public ChatClient chatClient(ChatClient.Builder chatClientBuilder) {\n        return chatClientBuilder.build();\n    }\n}<\/code><\/pre>\n<\/div>\n<p>Create a `BookAssistant.java` for sending book data into LLM and return the response. This class called by `BookService`.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">    public BookAIReply findBookGenre(String title, String authors) {\n\n        BookAIReply reply = chatClient.prompt()\n                .user(user -> user.text(\"What is the genre of the book {title} by {authors}. Provide the source url.\")\n                        .param(\"title\", title).param(\"authors\", authors))\n                .call().entity(BookAIReply.class);\n        return reply;\n    }\n\n\n\n    public BookAIReply findBookSummary(String title, String authors) {\n        BookAIReply reply = chatClient.prompt()\n                .user(user -> user.text(\"What is the summary of the book {title} by {authors}. Provide the source url.\")\n                        .param(\"title\", title).param(\"authors\", authors))\n                .call().entity(BookAIReply.class);\n        return reply;\n    }<\/code><\/pre>\n<\/div>\n<ul>\n<li>We use zero-shot prompting techniques. The zero-shot prompt directly instructs the model to perform a task without any additional examples to steer it.<\/li>\n<\/ul>\n<ul>\n<li><strong>`chatClient.prompt()`<\/strong>  <\/li>\n<\/ul>\n<p>   Starts building a prompt for the AI model.<\/p>\n<ul>\n<li><strong>`.user(user -> user.text(...).param(...))`<\/strong>  <\/li>\n<\/ul>\n<p>   - Defines the user message for the prompt.<br \/>\n   - User Role: Represents the user\u2019s input \u2013 their questions, commands, or statements to the AI.<br \/>\n   - `user.text(...)` sets the message template, using placeholders (`{title}`, `{authors}`).<br \/>\n   - `.param(\"title\", title)` and `.param(\"authors\", authors)` inject the actual values into the template.<\/p>\n<ul>\n<li><strong>`.call()`<\/strong><\/li>\n<\/ul>\n<p>   Executes the prompt, sending it to the AI model.<\/p>\n<ul>\n<li><strong>`.entity(BookAIReply.class)`<\/strong>  <\/li>\n<\/ul>\n<p>   Maps the AI\u2019s response to a `BookAIReply` Java object, making it easy to work with structured data.<\/p>\n<ul>\n<li>This method chain provides a fluent, type-safe way to interact with AI models using Spring AI, abstracting away the complexity of prompt construction and response parsing. The same pattern is used for other methods.<\/li>\n<\/ul>\n<p>And finally wire in the `BookAssistant` component into the `BookService`. <\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">BookAIReply reply = bookAssistant.findBookSummary(book.title(), book.authors());\n\nString summary = reply.value();\nString sourceUrl = reply.sourceUrl();<\/code><\/pre>\n<\/div>\n<p>You can find the code for this article on <a href=\"https:\/\/github.com\/alifruliarso\/book-inventory-withspringai\/tree\/main\">Github<\/a> and run the application from the command line with Maven.<\/p>\n<p><a href=\"\/wp-content\/uploads\/2026\/03\/springai-updatebooksummary.gif\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/03\/springai-updatebooksummary.gif\" alt=\"\" width=\"931\" height=\"614\" class=\"aligncenter size-full wp-image-55062\" \/><\/a><\/p>\n<p>GridDB Cloud query output.<\/p>\n<p><a href=\"\/wp-content\/uploads\/2026\/03\/griddbcloud_booksqueryoutput.png\"><img loading=\"lazy\" decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/03\/griddbcloud_booksqueryoutput.png\" alt=\"\" width=\"1587\" height=\"812\" class=\"aligncenter size-full wp-image-55061\" srcset=\"\/wp-content\/uploads\/2026\/03\/griddbcloud_booksqueryoutput.png 1587w, \/wp-content\/uploads\/2026\/03\/griddbcloud_booksqueryoutput-300x153.png 300w, \/wp-content\/uploads\/2026\/03\/griddbcloud_booksqueryoutput-1024x524.png 1024w, \/wp-content\/uploads\/2026\/03\/griddbcloud_booksqueryoutput-768x393.png 768w, \/wp-content\/uploads\/2026\/03\/griddbcloud_booksqueryoutput-1536x786.png 1536w, \/wp-content\/uploads\/2026\/03\/griddbcloud_booksqueryoutput-600x307.png 600w\" sizes=\"(max-width: 1587px) 100vw, 1587px\" \/><\/a><\/p>\n<h2 id=\"summary\">Summary<\/h2>\n<p>In this article, we have built a web application without touching a single line of JavaScript or HTML. We achieved this by combining Vaadin with Spring Boot. What's more, we make it even smarter by integrating Spring AI, giving it intelligent capabilities.<\/p>\n<p>Future enhancements:<\/p>\n<ul>\n<li>Adding data filtering<\/li>\n<li>Use pagination or lazy loading.<\/li>\n<li>Evaluate generative AI output.<\/li>\n<li>Enable natural language queries for book searches with semantic search.<\/li>\n<li>Add a voice assistant that responds to user commands.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Here is a project showcasing a simple yet powerful project: a smarter website for managing book collections. In this post, we&#8217;re going to build this web application from the ground up. We will use Spring Boot, Spring AI, Vaadin Flow to create it. And we will store the data in the cloud using GridDB Cloud. [&hellip;]<\/p>\n","protected":false},"author":41,"featured_media":55060,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[121],"tags":[],"class_list":["post-55057","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Building a Smarter Book Inventory using Vaadin and Spring AI | GridDB: Open Source Time Series Database for IoT<\/title>\n<meta name=\"description\" content=\"Here is a project showcasing a simple yet powerful project: a smarter website for managing book collections. In this post, we&#039;re going to build this web\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Smarter Book Inventory using Vaadin and Spring AI | GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"og:description\" content=\"Here is a project showcasing a simple yet powerful project: a smarter website for managing book collections. In this post, we&#039;re going to build this web\" \/>\n<meta property=\"og:url\" content=\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/\" \/>\n<meta property=\"og:site_name\" content=\"GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/griddbcommunity\/\" \/>\n<meta property=\"article:published_time\" content=\"2026-03-12T18:45:11+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.griddb.net\/wp-content\/uploads\/2026\/03\/cover-book.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"480\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"griddb-admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:site\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"griddb-admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/\"},\"author\":{\"name\":\"griddb-admin\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\"},\"headline\":\"Building a Smarter Book Inventory using Vaadin and Spring AI\",\"datePublished\":\"2026-03-12T18:45:11+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/\"},\"wordCount\":1751,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2026\/03\/cover-book.png\",\"articleSection\":[\"Blog\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/\",\"url\":\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/\",\"name\":\"Building a Smarter Book Inventory using Vaadin and Spring AI | GridDB: Open Source Time Series Database for IoT\",\"isPartOf\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2026\/03\/cover-book.png\",\"datePublished\":\"2026-03-12T18:45:11+00:00\",\"description\":\"Here is a project showcasing a simple yet powerful project: a smarter website for managing book collections. In this post, we're going to build this web\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/#primaryimage\",\"url\":\"\/wp-content\/uploads\/2026\/03\/cover-book.png\",\"contentUrl\":\"\/wp-content\/uploads\/2026\/03\/cover-book.png\",\"width\":1920,\"height\":480},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.griddb.net\/en\/#website\",\"url\":\"https:\/\/www.griddb.net\/en\/\",\"name\":\"GridDB: Open Source Time Series Database for IoT\",\"description\":\"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL\",\"publisher\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.griddb.net\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\",\"name\":\"Fixstars\",\"url\":\"https:\/\/www.griddb.net\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"contentUrl\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"width\":200,\"height\":83,\"caption\":\"Fixstars\"},\"image\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/griddbcommunity\/\",\"https:\/\/x.com\/GridDBCommunity\",\"https:\/\/www.linkedin.com\/company\/griddb-by-toshiba\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\",\"name\":\"griddb-admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g\",\"caption\":\"griddb-admin\"},\"url\":\"https:\/\/www.griddb.net\/en\/author\/griddb-admin\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Building a Smarter Book Inventory using Vaadin and Spring AI | GridDB: Open Source Time Series Database for IoT","description":"Here is a project showcasing a simple yet powerful project: a smarter website for managing book collections. In this post, we're going to build this web","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/","og_locale":"en_US","og_type":"article","og_title":"Building a Smarter Book Inventory using Vaadin and Spring AI | GridDB: Open Source Time Series Database for IoT","og_description":"Here is a project showcasing a simple yet powerful project: a smarter website for managing book collections. In this post, we're going to build this web","og_url":"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/","og_site_name":"GridDB: Open Source Time Series Database for IoT","article_publisher":"https:\/\/www.facebook.com\/griddbcommunity\/","article_published_time":"2026-03-12T18:45:11+00:00","og_image":[{"width":1920,"height":480,"url":"https:\/\/www.griddb.net\/wp-content\/uploads\/2026\/03\/cover-book.png","type":"image\/png"}],"author":"griddb-admin","twitter_card":"summary_large_image","twitter_creator":"@GridDBCommunity","twitter_site":"@GridDBCommunity","twitter_misc":{"Written by":"griddb-admin","Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/#article","isPartOf":{"@id":"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/"},"author":{"name":"griddb-admin","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233"},"headline":"Building a Smarter Book Inventory using Vaadin and Spring AI","datePublished":"2026-03-12T18:45:11+00:00","mainEntityOfPage":{"@id":"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/"},"wordCount":1751,"commentCount":0,"publisher":{"@id":"https:\/\/www.griddb.net\/en\/#organization"},"image":{"@id":"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2026\/03\/cover-book.png","articleSection":["Blog"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/","url":"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/","name":"Building a Smarter Book Inventory using Vaadin and Spring AI | GridDB: Open Source Time Series Database for IoT","isPartOf":{"@id":"https:\/\/www.griddb.net\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/#primaryimage"},"image":{"@id":"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2026\/03\/cover-book.png","datePublished":"2026-03-12T18:45:11+00:00","description":"Here is a project showcasing a simple yet powerful project: a smarter website for managing book collections. In this post, we're going to build this web","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/griddb.net\/en\/blog\/building-a-smarter-book-inventory-using-vaadin-and-spring-ai\/#primaryimage","url":"\/wp-content\/uploads\/2026\/03\/cover-book.png","contentUrl":"\/wp-content\/uploads\/2026\/03\/cover-book.png","width":1920,"height":480},{"@type":"WebSite","@id":"https:\/\/www.griddb.net\/en\/#website","url":"https:\/\/www.griddb.net\/en\/","name":"GridDB: Open Source Time Series Database for IoT","description":"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL","publisher":{"@id":"https:\/\/www.griddb.net\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.griddb.net\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.griddb.net\/en\/#organization","name":"Fixstars","url":"https:\/\/www.griddb.net\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/","url":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","contentUrl":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","width":200,"height":83,"caption":"Fixstars"},"image":{"@id":"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/griddbcommunity\/","https:\/\/x.com\/GridDBCommunity","https:\/\/www.linkedin.com\/company\/griddb-by-toshiba"]},{"@type":"Person","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233","name":"griddb-admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g","caption":"griddb-admin"},"url":"https:\/\/www.griddb.net\/en\/author\/griddb-admin\/"}]}},"_links":{"self":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/55057","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/comments?post=55057"}],"version-history":[{"count":3,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/55057\/revisions"}],"predecessor-version":[{"id":55066,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/55057\/revisions\/55066"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/media\/55060"}],"wp:attachment":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/media?parent=55057"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/categories?post=55057"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/tags?post=55057"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}