Build a GenAI Fitness Trainer App with Spring AI, Java and Vaadin Flow
Introduction
Back in March I wrote an article about building an A.I fitness trainer app using Python and LM Studio. While Python is not my main programming language, the recent developments in terms of new A.I frameworks like Langchain and LlamaIndex made it very easy for me to develop that application without advanced A.I or Python skills. Meanwhile, a different library started making waves in the software development community, and this time, it was in the Java corner of the internet! Java happens to be my main programming language and this "dark horse" of A.I frameworks is called Spring AI, now experiencing rapid growth in features and popularity especially thanks to some of the most influential Java developers contributing to the open source development and promotion. It came up on my radar a while ago and I knew instantly I will have to try it at some point. That time is now and what a better way to compare it to the Python way of doing things if not by creating the same application as I did back then?
Why Spring AI?
I know, I know, you are probably wondering why would somebody develop an A.I application using Java when such a large array of Python tools is already made available to us, especially since, for as long as I can remember, A.I development was pretty much synonymous to the Python programming language? The official answer can be found in the Spring AI documentation :
"The project was founded with the belief that the next wave of Generative AI applications will not be only for Python developers but will be ubiquitous across many programming languages." - Spring AI maintainers
In other words, as LLMs become more popular and present in our everyday lives, they will become part of the standard programming tool set, and as such, almost every developer will need the ability to integrate them in their project. Spring AI tries to see ahead of the current hype and already be a mature project by the time Java developers will be required by their manager to add A.I features to the project(no matter how relevant or not those features will be, but that is a different discussion).
In my opinion, there is also a second unofficial reason for the existence of Spring AI. From my experience, the Java community is one of the most stubborn(in a good way, I want to add!) about not migrating to other programming languages. So stubborn is the Java community that you can find project out there like Vaadin Flow(which I already covered in a separate article) that allows you to build frontends entirely in Java. Not only that, but since we are talking A.I, while in the beginning of the LLM revolution everybody was scrambling to learn Python and Langchain, Java developers were already busy porting everything to - you guessed it - Java, in the form of LangChain4J. This personal opinion, nevertheless, doesn't take anything away from Spring AI, which is one of the best managed new Open Source project I've seen recently!
Building the application
Prerequisites
For the application to run locally, you will need to install Ollama, allowing us to run LLMs locally. I don't want to go in too much detail about it, but if you don't have experience with running LLMs locally, I would recommend researching the topic a bit. Keep in mind these LLMs can be very resource heavy, so maybe you'll want to choose a different model depending on your system. After installation run the following command in a terminal:
ollama run llama3
This will download and run the popular Llama 3 model which has a considerable size of 4.7 GB, so depending on the speed of your internet connection, might take a while.
This is not an introductory Java tutorial, so I will assume you already have all the dependencies needed to run a Spring Boot application using Maven.
Coding the Java application
The final code of the application can be found on Github https://github.com/alexandru09/spring-ai-demo if you don't want to follow instructions step by step.
In order to start our project, we will first have to go to the Spring Initializr(https://start.spring.io) and select the following dependencies:
The real magic is done by the "Ollama" dependency which is part of the Spring AI project and will make it easy for us to interact with the Ollama server. The only dependency that we have to add besides that is the Vaadin project to create a simple UI using only Java code.
After downloading the starter project, unzipping the code and starting our preferred code editor, we should first specify the LLM model we have running locally in Ollama. To do this, after locating the resources/application.properties
files we should add the following line:
spring.ai.ollama.chat.model=llama3
Defining the POJOs
We'll need to define the POJO classes to map the response of the LLM. This will make sense in a minute, but for now, just be patient. The classes are Workout.java and Exercise.java:
public record Workout(String duration,
String difficulty,
String equipment,
String muscleGroup,
String instructions,
List<Exercise> exerciseList){ }
public record Exercise(String name,
String sets,
String repetitions,
String muscleGroup) { }
The definitions are pretty self explanatory, but long story short, the Workout POJO contains the general information about the workout and the list of Exercises.
Coding the Service
We will need a @Service
class to define our AI features, so we should create the com/example/spring_ai_demo/service
directory and define the WorkoutService.java
class:
// imports here
@Service
public class WorkoutService {
private final ChatClient chatClient;
public WorkoutService(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder
.defaultSystem("You are an experienced fitness trainer specialized in creating workouts for " +
"your clients. Provide balanced workouts within the confinements described by the user. " +
"Prioritize warming up and injury prevention. " +
"Provide the necessary guidance without going in too much detail." +
"Always adhere to the output format provided in the prompt.")
.build();
}
public Workout generateWorkout(String userRequest) {
return chatClient.prompt()
.user(userRequest)
.call()
.entity(Workout.class);
}
}
Here we can see several main features of Spring AI in action. First of all, the ChatClient
instance can be added using the Spring dependency injection mechanism, something familiar to all Spring developers. Second, we see one of the newest features(at the time of writing this article) added to the Spring AI project, the Fluent API. The ChatClient
instance is created in the WorkoutService
constructor in a functional, easy to use chain. It's here that we define the LLM's system prompt(using the defaultSystem()
function) which sets the tone for the LLM to know what we want from it.
In the generateWorkout
method we see how easy interacting with an LLM is. Using the same Fluent API, we can pass the user prompt in the user()
function and then specify the output format using probably one of the most useful features of this framework, the entity()
function. By specifying the Workout.class
type, a bit of prompt engineering happens behind the scenes and we are spared the tedious and boring task of mapping JSON to our desired POJO.
Create the UI using Vaadin Flow
The purpose of this application is to see Spring AI in action so I won't go into much detail about how Vaadin Flow works. For a more in depth look, check my older article about Vaadin Flow. For the scope of this tutorial, just know Vaadin Flow is a framework allowing us to write good looking frontends using just Java code.
We are only going to need one page for our use case, so this Java class is enough for us:
// imports here
@PageTitle("Workout Generator")
@Menu(icon = "line-awesome/svg/globe-solid.svg", order = 0)
@Route(value = "")
@RouteAlias(value = "")
public class ChatView extends VerticalLayout {
private final WorkoutService workoutService;
private VerticalLayout generatedWorkout;
public ChatView(WorkoutService workoutService) {
this.workoutService = workoutService;
TextField workoutDescription = new TextField("Describe the workout you want");
workoutDescription.setWidth("300px");
Button getWorkoutBtn = new Button("Get Workout");
generatedWorkout = new VerticalLayout();
getWorkoutBtn.addClickListener(e -> {
Workout workout = workoutService.generateWorkout(workoutDescription.getValue());
generatedWorkout.removeAll();
generatedWorkout.add(formatWorkoutResponse(workout));
});
getWorkoutBtn.addClickShortcut(Key.ENTER);
HorizontalLayout hl1 = new HorizontalLayout(Alignment.END, workoutDescription, getWorkoutBtn);
hl1.setWidthFull();
hl1.setJustifyContentMode ( FlexComponent.JustifyContentMode.CENTER );
add(hl1, generatedWorkout);
}
private static VerticalLayout formatWorkoutResponse(Workout workout) {
VerticalLayout layout = new VerticalLayout();
// Add workout details
layout.add(new H3("Workout Details"));
layout.add(new Div(new Span("Duration: "), new Span(workout.duration())));
layout.add(new Div(new Span("Difficulty: "), new Span(workout.difficulty())));
layout.add(new Div(new Span("Equipment: "), new Span(workout.equipment())));
layout.add(new Div(new Span("Muscle Group: "), new Span(workout.muscleGroup())));
layout.add(new Div(new Span("Instructions: "), new Span(workout.instructions())));
// Add a separator
layout.add(new Hr());
// Add exercise list
layout.add(new H3("Exercise List"));
for (Exercise exercise : workout.exerciseList()) {
VerticalLayout exerciseLayout = new VerticalLayout();
exerciseLayout.add(new Span(StringTemplate.STR."Name: \{exercise.name()}"));
exerciseLayout.add(new Span(StringTemplate.STR."Sets: \{exercise.sets()}"));
exerciseLayout.add(new Span(StringTemplate.STR."Reps: \{exercise.reps()}"));
exerciseLayout.add(new Span(StringTemplate.STR."Muscle Group: \{exercise.muscleGroup()}"));
exerciseLayout.add(new Hr());
layout.add(exerciseLayout);
}
// Adding some margin and padding for better spacing
layout.getStyle().set("padding", "10px");
layout.getStyle().set("margin", "10px");
layout.getStyle().set("border", "1px solid #ccc");
layout.getStyle().set("border-radius", "5px");
return layout;
}
}
Running the application
After starting our Java application, we should be able to navigate to http://localhost:8080/ and see the following page, prompting us to describe the type of workout we desire:
And this is how the result should look, in my case after prompting for an "upper body workout using only dumbbells":
Closing thoughts
I would have never thought adding AI features to Java apps would be so easy!
Python still has the advantage in the AI space because if you want to be at the forefront of AI development, Python holds the crown for the best tooling, but not everybody wants that. If you just want to add AI features to your existing application or if you are a Java developer creating a "GPT-wrapper" type of product, then I see no reason why you can't do that entirely in the Spring ecosystem using the tools you already know so well!
In the end, I would like to extend my gratitude to all the Spring AI contributors, most of which are also Spring veterans to begin with! It's thanks to their continuous effort and commitment that Java is still relevant today and we can all build state of the art applications using the language we know and like!
Subscribe to my newsletter
Read articles from Alexandru Tudor directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Alexandru Tudor
Alexandru Tudor
Curious Software Developer about all things Java and Python! Currently working as a Backend Software Engineer managing multiple microservices developed with Spring Boot 3 and Java 21.