|
| 1 | +--- |
| 2 | +title: Quick Start-Guide |
| 3 | +page-title: Quick Start-Guide | AI Chatbot with Vaadin |
| 4 | +description: A compact chat view with streaming, correct scrolling, and message context. |
| 5 | +meta-description: Hands-on tutorial - connect Vaadin to an LLM with Spring, build a streaming chat UI, and apply simple, reusable patterns for prompts, memory, and UX. |
| 6 | +order: 20 |
| 7 | +section-nav: badge-flow |
| 8 | +--- |
| 9 | + |
| 10 | + |
| 11 | += Quick Start-Guide: Add an AI Chat Bot to a Vaadin + Spring Boot Application [badge-flow]#Flow# |
| 12 | + |
| 13 | +This guide shows how to connect a Large Language Model (LLM) into a Vaadin application using Spring AI and Spring Boot. You'll build a minimal chat UI with Vaadin provided components **MessageList** and **MessageInput**, stream responses token-by-token, and keep a conversational tone in the dialog with the AI. |
| 14 | + |
| 15 | +image::images/chatbot-image.png[role=text-center] |
| 16 | + |
| 17 | +**Audience & style:** This quick guide is for Java developers and Vaadin beginners, as well as AI rookies. The sample application is implemented in Spring style and explains in small, practical steps how to integrate AI into Vaadin. Code snippets are available for copy and paste. |
| 18 | + |
| 19 | + |
| 20 | +== Prerequisites |
| 21 | + |
| 22 | +* Java 17+ |
| 23 | +* Spring Boot 3.5+ (or newer) |
| 24 | +* Vaadin 24.8+ |
| 25 | +* An OpenAI API key (`OPENAI_API_KEY`) |
| 26 | + |
| 27 | + |
| 28 | +== 1. Start from a Vaadin Spring Skeleton |
| 29 | + |
| 30 | +Download a Vaadin Spring starter from http://github.com/vaadin/skeleton-starter-flow-spring[GitHub], import it into your preferred IDE, and get it running in your environment. |
| 31 | + |
| 32 | +**Pro Tip**: Starting the application with Hotswap Agent improves your development lifecycle. |
| 33 | + |
| 34 | +Start with a cleaning and remove the default service `GreetService` and clear the existing UI content. You'll implement everything in `MainView`. |
| 35 | + |
| 36 | + |
| 37 | +== 2. Add Spring AI dependencies |
| 38 | + |
| 39 | +Add the Spring AI BOM and the OpenAI starter to import the necessary dependencies to your project. The BOM takes care of all Spring AI dependencies and provides the consistent version numbers to each sub dependency automatically. |
| 40 | + |
| 41 | +**Maven (`pom.xml`):** |
| 42 | + |
| 43 | +[source,xml] |
| 44 | +---- |
| 45 | +<dependencyManagement> |
| 46 | + ... |
| 47 | + <dependencies> |
| 48 | + <dependency> |
| 49 | + <groupId>org.springframework.ai</groupId> |
| 50 | + <artifactId>spring-ai-bom</artifactId> |
| 51 | + <version>1.0.1</version><!-- use the latest stable --> |
| 52 | + <type>pom</type> |
| 53 | + <scope>import</scope> |
| 54 | + </dependency> |
| 55 | + </dependencies> |
| 56 | + ... |
| 57 | +</dependencyManagement> |
| 58 | +
|
| 59 | +<dependencies> |
| 60 | + ... |
| 61 | + <!-- OpenAI LLM via Spring AI --> |
| 62 | + <dependency> |
| 63 | + <groupId>org.springframework.ai</groupId> |
| 64 | + <artifactId>spring-ai-starter-model-openai</artifactId> |
| 65 | + </dependency> |
| 66 | + ... |
| 67 | +</dependencies> |
| 68 | +---- |
| 69 | + |
| 70 | +*(Gradle users: import the Spring AI BOM and add the same starters.)* |
| 71 | + |
| 72 | + |
| 73 | +== 3. Configure Your OpenAI Credentials |
| 74 | + |
| 75 | +To access the API of OpenAI you need a license key. The preferred way to provide the key is through an environment variable, as this makes it available to other applications as well. After setting the environment variable on your system, refer to it from `application.properties` like this: |
| 76 | + |
| 77 | +[source,properties] |
| 78 | +---- |
| 79 | +spring.ai.openai.api-key=${OPENAI_API_KEY} |
| 80 | +# Optional: pick a model; adjust to what your account supports |
| 81 | +# spring.ai.openai.chat.options.model=gpt-5 |
| 82 | +---- |
| 83 | + |
| 84 | +**Tip:** use Spring profiles or your CI/CD's secret store for the key. |
| 85 | + |
| 86 | + |
| 87 | +== 4. Enable Vaadin Push |
| 88 | + |
| 89 | +To prevent end-users from sitting in front of a blank screen waiting for a response, you'll stream tokens asynchronously and update the UI live with response tokens. To do this, you need to enable server push: |
| 90 | + |
| 91 | +[source,java] |
| 92 | +---- |
| 93 | +// src/main/java/org/vaadin/example/Application.java |
| 94 | +
|
| 95 | +import com.vaadin.flow.component.page.AppShellConfigurator; |
| 96 | +import com.vaadin.flow.component.page.Push; |
| 97 | +import com.vaadin.flow.router.PageTitle; |
| 98 | +import com.vaadin.flow.theme.Theme; |
| 99 | +import org.springframework.boot.SpringApplication; |
| 100 | +import org.springframework.boot.autoconfigure.SpringBootApplication; |
| 101 | +
|
| 102 | +@Theme("my-theme") |
| 103 | +@PageTitle("AI in Vaadin") |
| 104 | +@SpringBootApplication |
| 105 | +@Push |
| 106 | +public class Application implements AppShellConfigurator { |
| 107 | +
|
| 108 | + public static void main(String[] args) { |
| 109 | + SpringApplication.run(Application.class, args); |
| 110 | + } |
| 111 | +} |
| 112 | +---- |
| 113 | + |
| 114 | + |
| 115 | +== 5. Create the Chat service (Spring AI) |
| 116 | + |
| 117 | +Create a new class called **ChatService** and annotate it with `@Service`. This service builds a `ChatClient` with a **ChatMemory** advisor in the constructor and exposes a **reactive stream** of tokens. |
| 118 | + |
| 119 | +[source,java] |
| 120 | +---- |
| 121 | +// src/main/java/org/vaadin/example/ChatService.java |
| 122 | +package org.vaadin.example; |
| 123 | +
|
| 124 | +import org.springframework.ai.chat.client.ChatClient; |
| 125 | +import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; |
| 126 | +import org.springframework.ai.chat.memory.ChatMemory; |
| 127 | +import org.springframework.stereotype.Service; |
| 128 | +import reactor.core.publisher.Flux; |
| 129 | +
|
| 130 | +@Service |
| 131 | +public class ChatService { |
| 132 | +
|
| 133 | + private final ChatClient chatClient; |
| 134 | +
|
| 135 | + public ChatService(ChatClient.Builder chatClientBuilder, |
| 136 | + ChatMemory chatMemory) { |
| 137 | + // Add a memory advisor to the chat client |
| 138 | + var chatMemoryAdvisor = MessageChatMemoryAdvisor |
| 139 | + .builder(chatMemory) |
| 140 | + .build(); |
| 141 | +
|
| 142 | + // Build the chat client |
| 143 | + chatClient = chatClientBuilder |
| 144 | + .defaultAdvisors(chatMemoryAdvisor) |
| 145 | + .build(); |
| 146 | + } |
| 147 | +
|
| 148 | + public Flux<String> chatStream(String userInput, String chatId) { |
| 149 | + return chatClient.prompt() |
| 150 | + .advisors(advisorSpec -> |
| 151 | + advisorSpec.param(ChatMemory.CONVERSATION_ID, chatId) |
| 152 | + ) |
| 153 | + .user(userInput) |
| 154 | + .stream() |
| 155 | + .content(); |
| 156 | + } |
| 157 | +} |
| 158 | +
|
| 159 | +---- |
| 160 | + |
| 161 | +Why a chat memory? **ChatMemory** keeps context of the conversations so users don't have to repeat themselves. The `chatId` keeps the context for a specific chat and doesn't share it with other chats and users. |
| 162 | + |
| 163 | + |
| 164 | +== 6. Build the Chat UI with Vaadin |
| 165 | + |
| 166 | +Use `MessageList` to render the conversation as Markdown and `MessageInput` to handle the user prompts. Wrap the list in a `Scroller` so long chats don't grow the layout beyond the browser window: |
| 167 | + |
| 168 | +[source,java] |
| 169 | +---- |
| 170 | +// src/main/java/org/vaadin/example/MainView.java |
| 171 | +package com.example.application.views.chatbot; |
| 172 | +
|
| 173 | +import com.example.application.services.ChatService; |
| 174 | +import com.vaadin.flow.component.Composite; |
| 175 | +import com.vaadin.flow.component.messages.MessageInput; |
| 176 | +import com.vaadin.flow.component.messages.MessageList; |
| 177 | +import com.vaadin.flow.component.messages.MessageListItem; |
| 178 | +import com.vaadin.flow.component.orderedlayout.Scroller; |
| 179 | +import com.vaadin.flow.component.orderedlayout.VerticalLayout; |
| 180 | +import com.vaadin.flow.router.Menu; |
| 181 | +import com.vaadin.flow.router.PageTitle; |
| 182 | +import com.vaadin.flow.router.Route; |
| 183 | +import com.vaadin.flow.router.RouteAlias; |
| 184 | +import org.vaadin.lineawesome.LineAwesomeIconUrl; |
| 185 | +
|
| 186 | +import java.time.Instant; |
| 187 | +import java.util.UUID; |
| 188 | +
|
| 189 | +@PageTitle("Chat Bot") |
| 190 | +@Route("") |
| 191 | +@RouteAlias("chat-bot") |
| 192 | +@Menu(order = 0, icon = LineAwesomeIconUrl.ROBOT_SOLID) |
| 193 | +public class ChatBotView extends Composite<VerticalLayout> { |
| 194 | +
|
| 195 | + private final ChatService chatService; |
| 196 | + private final MessageList messageList; |
| 197 | + private final String chatId = UUID.randomUUID().toString(); |
| 198 | +
|
| 199 | + public ChatBotView(ChatService chatService) { |
| 200 | + this.chatService = chatService; |
| 201 | +
|
| 202 | + //Create a scrolling MessageList |
| 203 | + messageList = new MessageList(); |
| 204 | + var scroller = new Scroller(messageList); |
| 205 | + scroller.setHeightFull(); |
| 206 | + getContent().addAndExpand(scroller); |
| 207 | +
|
| 208 | + //create a MessageInput and set a submit-listener |
| 209 | + var messageInput = new MessageInput(); |
| 210 | + messageInput.addSubmitListener(this::onSubmit); |
| 211 | + messageInput.setWidthFull(); |
| 212 | +
|
| 213 | + getContent().add(messageInput); |
| 214 | + } |
| 215 | +
|
| 216 | + private void onSubmit(MessageInput.SubmitEvent submitEvent) { |
| 217 | + //create and handle a prompt message |
| 218 | + var promptMessage = new MessageListItem(submitEvent.getValue(), Instant.now(), "User"); |
| 219 | + promptMessage.setUserColorIndex(0); |
| 220 | + messageList.addItem(promptMessage); |
| 221 | +
|
| 222 | + //create and handle the response message |
| 223 | + var responseMessage = new MessageListItem("", Instant.now(), "Bot"); |
| 224 | + responseMessage.setUserColorIndex(1); |
| 225 | + messageList.addItem(responseMessage); |
| 226 | +
|
| 227 | + //append a response message to the existing UI |
| 228 | + var userPrompt = submitEvent.getValue(); |
| 229 | + var uiOptional = submitEvent.getSource().getUI(); |
| 230 | + var ui = uiOptional.orElse(null); //implementation via ifPresent also possible |
| 231 | +
|
| 232 | + if (ui != null) { |
| 233 | + chatService.chatStream(userPrompt, chatId) |
| 234 | + .subscribe(token -> |
| 235 | + ui.access(() -> |
| 236 | + responseMessage.appendText(token))); |
| 237 | + } |
| 238 | + } |
| 239 | +} |
| 240 | +
|
| 241 | +---- |
| 242 | + |
| 243 | +**Key UI patterns used here:** |
| 244 | + |
| 245 | +* **Dialog character:** display prompts and responses separately so the difference remains visible. |
| 246 | +* **Streaming output:** show tokens as they arrive for perceived performance. |
| 247 | +* **Markdown rendering:** richer answers (lists, code blocks, emojis). |
| 248 | +* **Sticky scroll:** keep the latest answer in view. |
| 249 | + |
| 250 | + |
| 251 | +== 7. Run & Iterate |
| 252 | + |
| 253 | +Start the application, open the browser, and try your first prompts. |
| 254 | + |
| 255 | + |
| 256 | +== What You Built |
| 257 | + |
| 258 | +* A production-ready **chat bot** using Vaadin components |
| 259 | +* **Token-by-token streaming** with Vaadin Push |
| 260 | +* **Conversation memory** via Spring AI advisors |
| 261 | + |
| 262 | + |
| 263 | +== Next Possible Steps |
| 264 | + |
| 265 | +* Add a **system prompt** field to steer the assistant (e.g., tone, persona). |
| 266 | +* Add **clear chat** and **export** actions. |
| 267 | +* Add **feedback** to evaluate responses |
| 268 | +* Support **attachments** and **tool calls** (retrieval, functions). |
| 269 | +* Log prompts/responses for observability. |
| 270 | + |
| 271 | + |
| 272 | +== Troubleshooting |
| 273 | + |
| 274 | +* **No streaming updates?** Ensure `@Push` is present and check reverse proxy/WebSocket settings. |
| 275 | +* **401 Exception from OpenAI?** Verify `OPENAI_API_KEY` and environment injection in your run configuration. |
| 276 | + |
| 277 | + |
| 278 | +== Complete File List Recap |
| 279 | + |
| 280 | +* `src/main/java/org/vaadin/example/Application.java` — Spring Boot + `@Push` |
| 281 | +* `src/main/java/org/vaadin/example/ChatService.java` — Spring AI client + memory |
| 282 | +* `src/main/java/org/vaadin/example/MainView.java` — Vaadin chat UI |
| 283 | +* `src/main/resources/application.properties` — OpenAI config |
| 284 | +* `pom.xml` — Vaadin + Spring AI dependencies |
| 285 | + |
| 286 | +That's it — your Vaadin application now speaks AI. 🚀 |
0 commit comments