Recently, I added a simple chatbot to my Finovara app that answers users’ finance-related questions.
No AI, no NLP libraries — just clean architecture, enums, and text files.
It works surprisingly well for the limited scope we need. Here’s how it’s built.
The Idea
The chatbot works like this:
- A user asks a question.
- The system normalizes the input.
- It maps the question to a predefined entry.
- The entry corresponds to a SmartReportType enum.
- The enum is handled by a handler class.
- The handler generates the actual response.
This makes the system:
- Predictable
- Easy to extend
- Fast and lightweight
Core Architecture
The main entry point is:
public String generateResponse(String email, String userQuestion) {
User user = userManagerService.getUserByEmailOrThrow(email);
SmartReportType type = smartReportQuestionService.getTypeFromQuestion(userQuestion);
if (type == null)
return "I’m not smart enough to answer this question. Please ask a question from the question book!";
SmartReportHandler handler = handlers.get(type);
if (handler == null) {
return "Report type not supported";
}
return handler.generate(user.getId());
}
Here’s what’s happening:
The question is mapped to a SmartReportType enum.
Each enum has its own handler.
The handler generates a response based on templates and user data.
This is basically a Strategy Pattern in action.
Mapping Questions to Enums
Instead of hardcoding questions in Java, they are stored in TXT files:
MONTH_SPENDING | how much did i spend this month
MONTH_SPENDING | monthly expenses
AVERAGE_DAY_SPENDING | average daily spending
EXPENSE_RATE | what percentage of my income is spent
SAVINGS_RATE | how much did i save
Loader Service:
questionMap.put(question, SmartReportType.valueOf(enumName));
Normalization:
private String normalize(String text) {
return text.toLowerCase()
.trim()
.replace("?", "");
}
Thanks to this:
- “How much did I spend?”
- “how much did i spend”
- “HOW MUCH DID I SPEND??”
all map to the same key.
Handlers: Business Logic
Each enum type has its own handler:
public interface SmartReportHandler {
SmartReportType getType();
String generate(Long userId);
}
Handlers are responsible for fetching user data and generating responses.
Example: Expense Rate
BigDecimal total = sumExpenses.multiply(BigDecimal.valueOf(100))
.divide(sumRevenue, 2, RoundingMode.HALF_UP);
String template = templateService.getRandomResponse(SmartReportType.EXPENSE_RATE);
return template.replace("{amount}", total.toString());
Dynamic Responses (Templates)
Responses are stored in text files instead of being hardcoded:
You spent {amount} this month.
Your total expenses are {amount}.
Your savings rate is {amount}%.
Randomization makes replies feel less repetitive:
return responses.get(random.nextInt(responses.size()));
*Thanks for reading! *
If you’re curious to see the full project or try it yourself, check out the code on GitHub:
M4rc1nek
/
finovara-backend
Backend service for a personal finance management application
Finovara
Backend platform for a personal finance management application, built with Java 25 and Spring Boot 4 as a set of microservices.
About the Project
Finovara is a personal finance platform designed to help users take full control of their money. The backend exposes a secure REST API that powers tracking of income and expenses, budget management, savings goals, shared accounts and financial reporting.
The platform is built as a set of independent services, each responsible for a separate area of the domain, so that individual parts of the system can be developed, deployed and scaled on their own. Security is a central concern: authentication is based on JWT, sessions are tracked per device, and user activity is recorded in an audit log.
Key Features
Accounts and Security
- Authentication and Authorization - stateless JWT security built on Spring Security, with an access and refresh token flow.
- Device and Session Tracking…
Top comments (0)