<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Marcin Parśniak</title>
    <description>The latest articles on DEV Community by Marcin Parśniak (@m4rc1nek).</description>
    <link>https://dev.to/m4rc1nek</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3785129%2F8f8b1740-a32a-4bfe-849a-be69039a0fb7.jpg</url>
      <title>DEV Community: Marcin Parśniak</title>
      <link>https://dev.to/m4rc1nek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/m4rc1nek"/>
    <language>en</language>
    <item>
      <title>Finovara - Building a Simple Chatbot in Spring Boot</title>
      <dc:creator>Marcin Parśniak</dc:creator>
      <pubDate>Wed, 25 Mar 2026 16:37:24 +0000</pubDate>
      <link>https://dev.to/m4rc1nek/finovara-building-a-simple-chatbot-in-spring-boot-3a96</link>
      <guid>https://dev.to/m4rc1nek/finovara-building-a-simple-chatbot-in-spring-boot-3a96</guid>
      <description>&lt;p&gt;Recently, I added a simple chatbot to my &lt;strong&gt;Finovara&lt;/strong&gt; app that answers users’ finance-related questions.&lt;/p&gt;

&lt;p&gt;No AI, no NLP libraries — just clean architecture, enums, and text files.  &lt;/p&gt;

&lt;p&gt;It works surprisingly well for the limited scope we need. Here’s how it’s built.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;The chatbot works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A user asks a question.
&lt;/li&gt;
&lt;li&gt;The system &lt;strong&gt;normalizes the input&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;It maps the question to a predefined entry.
&lt;/li&gt;
&lt;li&gt;The entry corresponds to a &lt;strong&gt;SmartReportType enum&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;The enum is handled by a &lt;strong&gt;handler class&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;The handler generates the actual response.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This makes the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Predictable
&lt;/li&gt;
&lt;li&gt;Easy to extend
&lt;/li&gt;
&lt;li&gt;Fast and lightweight
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Core Architecture
&lt;/h2&gt;

&lt;p&gt;The main entry point is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;generateResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userQuestion&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userManagerService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserByEmailOrThrow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;SmartReportType&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smartReportQuestionService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTypeFromQuestion&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userQuestion&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"I’m not smart enough to answer this question. Please ask a question from the question book!"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="nc"&gt;SmartReportHandler&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Report type not supported"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here’s what’s happening:&lt;/p&gt;

&lt;p&gt;The question is mapped to a SmartReportType enum.&lt;br&gt;
Each enum has its own handler.&lt;br&gt;
The handler generates a response based on templates and user data.&lt;/p&gt;

&lt;p&gt;This is basically a Strategy Pattern in action.&lt;/p&gt;
&lt;h2&gt;
  
  
  Mapping Questions to Enums
&lt;/h2&gt;

&lt;p&gt;Instead of hardcoding questions in Java, they are stored in &lt;strong&gt;TXT files&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Loader Service:
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;questionMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SmartReportType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enumName&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Normalization:
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toLowerCase&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"?"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Thanks to this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“How much did I spend?”&lt;/li&gt;
&lt;li&gt;“how much did i spend”&lt;/li&gt;
&lt;li&gt;“HOW MUCH DID I SPEND??”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;all map to the same key.&lt;/p&gt;
&lt;h2&gt;
  
  
  Handlers: Business Logic
&lt;/h2&gt;

&lt;p&gt;Each enum type has its own handler:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;SmartReportHandler&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;SmartReportType&lt;/span&gt; &lt;span class="nf"&gt;getType&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Handlers are responsible for fetching user data and generating responses.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example: Expense Rate
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sumExpenses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;multiply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;divide&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sumRevenue&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RoundingMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;HALF_UP&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;templateService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRandomResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SmartReportType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;EXPENSE_RATE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{amount}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Dynamic Responses (Templates)
&lt;/h2&gt;

&lt;p&gt;Responses are stored in text files instead of being hardcoded:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You spent {amount} this month.
Your total expenses are {amount}.
Your savings rate is {amount}%.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Randomization makes replies &lt;strong&gt;feel less repetitive&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nextInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;*&lt;em&gt;Thanks for reading!  *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you’re curious to see the full project or try it yourself, check out the code on &lt;strong&gt;GitHub&lt;/strong&gt;:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/M4rc1nek" rel="noopener noreferrer"&gt;
        M4rc1nek
      &lt;/a&gt; / &lt;a href="https://github.com/M4rc1nek/finovara-backend" rel="noopener noreferrer"&gt;
        finovara-backend
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Backend service for a personal finance management application
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Finovara&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Finovara&lt;/strong&gt; is a financial management platform designed to help users effectively track
analyze, and optimize their income, expenses, and savings
The application provides a secure, bank-like experience focused on transparency,
financial awareness, and long-term money planning.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🎯 Purpose of the Application&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Finovara aims to support users in making better financial decisions by offering
clear insights into their financial activity and helping them maintain control
over their budgets and savings.&lt;/p&gt;
&lt;p&gt;The platform focuses on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;organizing income and expenses in a structured way&lt;/li&gt;
&lt;li&gt;visualizing financial data through charts and statistics&lt;/li&gt;
&lt;li&gt;supporting saving goals and spending limits&lt;/li&gt;
&lt;li&gt;providing a virtual wallet concept for daily financial management&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Key Features&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Secure user authentication and authorization&lt;/li&gt;
&lt;li&gt;Income and expense tracking&lt;/li&gt;
&lt;li&gt;Categorization of financial operations&lt;/li&gt;
&lt;li&gt;Interactive charts and financial statistics&lt;/li&gt;
&lt;li&gt;Reports summarizing spending and income trends&lt;/li&gt;
&lt;li&gt;Virtual wallet management&lt;/li&gt;
&lt;li&gt;Savings goals (e.g. piggy banks)&lt;/li&gt;
&lt;li&gt;Spending limits and budget control&lt;/li&gt;
&lt;li&gt;Scalable architecture prepared for future financial…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/M4rc1nek/finovara-backend" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>java</category>
      <category>ai</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Deploying Finovara on Docker and fixing a critical bug.</title>
      <dc:creator>Marcin Parśniak</dc:creator>
      <pubDate>Fri, 20 Mar 2026 21:35:33 +0000</pubDate>
      <link>https://dev.to/m4rc1nek/deploying-finovara-on-docker-and-fixing-a-critical-bug-2k5n</link>
      <guid>https://dev.to/m4rc1nek/deploying-finovara-on-docker-and-fixing-a-critical-bug-2k5n</guid>
      <description>&lt;p&gt;I recently decided to move my Spring Boot app &lt;strong&gt;Finovara&lt;/strong&gt;  to Docker.&lt;/p&gt;

&lt;p&gt;At first it was just supposed to be a small improvement to the setup. &lt;br&gt;
In reality, it turned into a debugging session that took way longer than I expected.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why I even bothered with Docker
&lt;/h2&gt;

&lt;p&gt;There were a few reasons behind this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I wanted &lt;strong&gt;more control over the environment&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;I needed a &lt;strong&gt;separate database (&lt;code&gt;finovara-test&lt;/code&gt;)&lt;/strong&gt; for automated tests&lt;/li&gt;
&lt;li&gt;I was tired of manually setting everything up locally&lt;/li&gt;
&lt;li&gt;and I wanted something closer to a &lt;strong&gt;real production setup&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Initial setup
&lt;/h2&gt;

&lt;p&gt;I started with a simple PostgreSQL container and connected my app to it.&lt;/p&gt;

&lt;p&gt;Here’s a simplified version of what I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;finovara-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;finovara-db&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;finovara&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_PASSWORD}&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;postgres"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Dockerfile:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; eclipse-temurin:21-jdk&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; target/*.jar app.jar&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Everything started without issues.&lt;br&gt;
Database was running, app connected, no errors.&lt;/p&gt;

&lt;p&gt;So I thought I was done.&lt;/p&gt;


&lt;h2&gt;
  
  
  The moment things got weird
&lt;/h2&gt;

&lt;p&gt;I made some changes in the app, rebuilt everything, ran it again and nothing changed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same responses&lt;/li&gt;
&lt;li&gt;same data&lt;/li&gt;
&lt;li&gt;same behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point I was pretty sure I messed something up.&lt;/p&gt;


&lt;h2&gt;
  
  
  What I thought vs what it actually was
&lt;/h2&gt;

&lt;p&gt;My first guess was Docker caching.&lt;br&gt;
Seemed like the obvious explanation.&lt;/p&gt;

&lt;p&gt;But after digging a bit more, it turned out to be something else entirely.&lt;/p&gt;

&lt;p&gt;The app was connecting to &lt;strong&gt;a completely different database&lt;/strong&gt; than I expected.&lt;/p&gt;

&lt;p&gt;Everything looked correct:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same DB name&lt;/li&gt;
&lt;li&gt;same user&lt;/li&gt;
&lt;li&gt;same config&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So from the outside it looked like everything was fine, but I was basically working on one database and checking another.&lt;/p&gt;


&lt;h2&gt;
  
  
  Fixing it
&lt;/h2&gt;

&lt;p&gt;The fix wasn’t one single thing, more like a combination of small adjustments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I &lt;strong&gt;changed the password&lt;/strong&gt; in my &lt;code&gt;.env&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;I &lt;strong&gt;changed the port&lt;/strong&gt;, because something else was already using 5432&lt;/li&gt;
&lt;li&gt;I cleaned up and adjusted the &lt;strong&gt;docker-compose config&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;and most importantly, I actually verified which database the app connects to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that, everything finally made sense again and changes started showing up immediately.&lt;/p&gt;

&lt;p&gt;Thanks for reading and visit my github!&lt;br&gt;


&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/M4rc1nek" rel="noopener noreferrer"&gt;
        M4rc1nek
      &lt;/a&gt; / &lt;a href="https://github.com/M4rc1nek/finovara-backend" rel="noopener noreferrer"&gt;
        finovara-backend
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Backend service for a personal finance management application
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Finovara&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Finovara&lt;/strong&gt; is a financial management platform designed to help users effectively track
analyze, and optimize their income, expenses, and savings
The application provides a secure, bank-like experience focused on transparency,
financial awareness, and long-term money planning.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🎯 Purpose of the Application&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Finovara aims to support users in making better financial decisions by offering
clear insights into their financial activity and helping them maintain control
over their budgets and savings.&lt;/p&gt;
&lt;p&gt;The platform focuses on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;organizing income and expenses in a structured way&lt;/li&gt;
&lt;li&gt;visualizing financial data through charts and statistics&lt;/li&gt;
&lt;li&gt;supporting saving goals and spending limits&lt;/li&gt;
&lt;li&gt;providing a virtual wallet concept for daily financial management&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Key Features&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Secure user authentication and authorization&lt;/li&gt;
&lt;li&gt;Income and expense tracking&lt;/li&gt;
&lt;li&gt;Categorization of financial operations&lt;/li&gt;
&lt;li&gt;Interactive charts and financial statistics&lt;/li&gt;
&lt;li&gt;Reports summarizing spending and income trends&lt;/li&gt;
&lt;li&gt;Virtual wallet management&lt;/li&gt;
&lt;li&gt;Savings goals (e.g. piggy banks)&lt;/li&gt;
&lt;li&gt;Spending limits and budget control&lt;/li&gt;
&lt;li&gt;Scalable architecture prepared for future financial…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/M4rc1nek/finovara-backend" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




</description>
      <category>devops</category>
      <category>docker</category>
      <category>java</category>
      <category>programming</category>
    </item>
    <item>
      <title>Liquibase in Spring Boot – Developer's Guide</title>
      <dc:creator>Marcin Parśniak</dc:creator>
      <pubDate>Tue, 10 Mar 2026 08:05:12 +0000</pubDate>
      <link>https://dev.to/m4rc1nek/liquibase-in-spring-boot-developers-guide-3o06</link>
      <guid>https://dev.to/m4rc1nek/liquibase-in-spring-boot-developers-guide-3o06</guid>
      <description>&lt;h2&gt;
  
  
  Liquibase in Spring Boot – Developer's Guide
&lt;/h2&gt;

&lt;p&gt;Managing database schema changes can seem simple at first… until it isn’t. A quick &lt;code&gt;ALTER TABLE&lt;/code&gt; here and there might work with one developer, but once you have multiple environments—dev, staging, prod—things get messy fast.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Liquibase&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;In this guide, I’ll walk you through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Liquibase is and why it matters&lt;/li&gt;
&lt;li&gt;Setting it up in Spring Boot&lt;/li&gt;
&lt;li&gt;Writing migrations safely&lt;/li&gt;
&lt;li&gt;Managing different environments&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What is Liquibase?
&lt;/h2&gt;

&lt;p&gt;Liquibase is essentially &lt;strong&gt;Git for your database schema&lt;/strong&gt;. Instead of running SQL scripts manually and hoping everyone runs them correctly, you define changes in a structured format and Liquibase tracks which ones have been applied.&lt;/p&gt;

&lt;p&gt;Example XML changeset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;changeSet&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"add-phone-number"&lt;/span&gt; &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"dev"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;addColumn&lt;/span&gt; &lt;span class="na"&gt;tableName=&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"phone_number"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"varchar(20)"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/addColumn&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/changeSet&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Liquibase keeps track of applied migrations in a table called &lt;code&gt;DATABASECHANGELOG&lt;/code&gt;, ensuring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changes are applied only once&lt;/li&gt;
&lt;li&gt;Order is maintained&lt;/li&gt;
&lt;li&gt;Rollbacks are possible&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why use Liquibase with Spring Boot?
&lt;/h2&gt;

&lt;p&gt;Some key benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Consistent environments&lt;/strong&gt; – dev, staging, and prod stay in sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration history&lt;/strong&gt; – see who changed what and when&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic migrations&lt;/strong&gt; – Spring Boot can run them on startup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollbacks&lt;/strong&gt; – undo changes safely if needed&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Adding Liquibase to your project
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Maven:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.liquibase&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;liquibase-core&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Gradle:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.liquibase:liquibase-core'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring Boot will automatically detect Liquibase and run migrations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;application.yml&lt;/code&gt; (safe YAML formatting):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;liquibase&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;change-log&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;classpath:/db/changelog/changelog.xml"&lt;/span&gt;
    &lt;span class="na"&gt;contexts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dev"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical project structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resources
 └ db
    └ changelog
        └ changelog.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Master changelog with multiple changesets
&lt;/h2&gt;

&lt;p&gt;Instead of separate XML files, you can put all changesets in a single file: &lt;code&gt;changelog.xml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;databaseChangeLog&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.liquibase.org/xml/ns/dbchangelog"&lt;/span&gt;
                   &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
                   &lt;span class="na"&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"http://www.liquibase.org/xml/ns/dbchangelog
                   http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;changeSet&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"create-users"&lt;/span&gt; &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"you"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;createTable&lt;/span&gt; &lt;span class="na"&gt;tableName=&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"bigint"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;constraints&lt;/span&gt; &lt;span class="na"&gt;primaryKey=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/column&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"varchar(255)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;constraints&lt;/span&gt; &lt;span class="na"&gt;nullable=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/column&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"created_at"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"timestamp"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/createTable&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/changeSet&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;changeSet&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"add-email-index"&lt;/span&gt; &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"you"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;createIndex&lt;/span&gt; &lt;span class="na"&gt;indexName=&lt;/span&gt;&lt;span class="s"&gt;"idx_users_email"&lt;/span&gt; &lt;span class="na"&gt;tableName=&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/createIndex&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/changeSet&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;changeSet&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"add-phone-column"&lt;/span&gt; &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"you"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;addColumn&lt;/span&gt; &lt;span class="na"&gt;tableName=&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"phone_number"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"varchar(20)"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/addColumn&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;rollback&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;dropColumn&lt;/span&gt; &lt;span class="na"&gt;tableName=&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt; &lt;span class="na"&gt;columnName=&lt;/span&gt;&lt;span class="s"&gt;"phone_number"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/rollback&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/changeSet&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;changeSet&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"insert-test-data"&lt;/span&gt; &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"you"&lt;/span&gt; &lt;span class="na"&gt;context=&lt;/span&gt;&lt;span class="s"&gt;"you"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;insert&lt;/span&gt; &lt;span class="na"&gt;tableName=&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt; &lt;span class="na"&gt;valueNumeric=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"test@example.com"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/insert&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/changeSet&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/databaseChangeLog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Using SQL instead of XML
&lt;/h2&gt;

&lt;p&gt;You can also write all changesets in a single formatted SQL file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- liquibase formatted sql&lt;/span&gt;
&lt;span class="c1"&gt;-- changeset dev:create-users&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- changeset dev:add-email-index&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_users_email&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- changeset dev:add-phone-column&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- rollback: ALTER TABLE users DROP COLUMN phone_number;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Best practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never edit executed migrations&lt;/strong&gt; – migrations are immutable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One changeset = one change&lt;/strong&gt; – separate tables, columns, and indexes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number your files if using multiple files&lt;/strong&gt; – avoids Git conflicts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test locally&lt;/strong&gt; – drop the DB, run the app, ensure migrations work from scratch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be careful on production&lt;/strong&gt; – avoid &lt;code&gt;ALTER COLUMN TYPE&lt;/code&gt;, &lt;code&gt;DROP COLUMN&lt;/code&gt;, or &lt;code&gt;NOT NULL&lt;/code&gt; on large tables&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Liquibase vs Flyway
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Liquibase&lt;/th&gt;
&lt;th&gt;Flyway&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;XML/YAML/JSON&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;td&gt;✖&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollback&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;td&gt;limited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Flyway&lt;/strong&gt; is simpler but  &lt;strong&gt;Liquibase&lt;/strong&gt; is more flexible for enterprise setups.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Liquibase is a lifesaver for keeping &lt;strong&gt;database schema consistent&lt;/strong&gt; across environments. Stick to the golden rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;immutable migrations&lt;/li&gt;
&lt;li&gt;one change per changeset&lt;/li&gt;
&lt;li&gt;test migrations locally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Thanks for reading!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>programming</category>
      <category>java</category>
      <category>sql</category>
    </item>
    <item>
      <title>Revenue test (JUnit5) - Finovara</title>
      <dc:creator>Marcin Parśniak</dc:creator>
      <pubDate>Sat, 07 Mar 2026 10:56:13 +0000</pubDate>
      <link>https://dev.to/m4rc1nek/revenue-test-junit5-finovara-14mg</link>
      <guid>https://dev.to/m4rc1nek/revenue-test-junit5-finovara-14mg</guid>
      <description>&lt;h2&gt;
  
  
  Hello!
&lt;/h2&gt;

&lt;p&gt;After the latest update to my project, I decided it was the perfect time to finally start writing tests for &lt;strong&gt;Finovara&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So far, I've written tests for the main methods. &lt;br&gt;
&lt;strong&gt;On average, I run 2-3 tests per method, which seems like a good compromise.&lt;/strong&gt; I cover both the happy path and a few edge cases and alternate flows—basically, &lt;strong&gt;I try to make sure everything works as expected&lt;/strong&gt;, regardless of the situation.&lt;/p&gt;

&lt;p&gt;Overall, I'm aiming for &lt;strong&gt;70-80% code coverage&lt;/strong&gt;, though this may change as I add more tests and improve what I already have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of a simple test:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;shouldAddRevenueSuccessfully&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;RevenueDTO&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RevenueDTO&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;RevenueCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SALARY&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"test income"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"test@test.com"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clock&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;thenReturn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Clock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;systemDefaultZone&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userManagerService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserByEmailOrThrow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;thenReturn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;revenueService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addRevenue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;walletService&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;addBalanceToWallet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revenueActivityService&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;createRevenueActivity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RevenueActivityType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ADDED_REVENUE&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Revenue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revenueRepository&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Revenue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revenueScoringService&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;recalculateScore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;autoPaymentsService&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;handleRevenuePiggyBankAutomation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;AutoPaymentsMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLY&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Next, I plan to write expense tests. I'll see how it goes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thanks for reading&lt;/strong&gt;, and please visit my GitHub: &lt;strong&gt;

&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;a href="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;&lt;/a&gt;
      &lt;a href="https://github.com/M4rc1nek" rel="noopener noreferrer"&gt;
        M4rc1nek
      &lt;/a&gt; / &lt;a href="https://github.com/M4rc1nek/finovara-backend" rel="noopener noreferrer"&gt;
        finovara-backend
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Backend service for a personal finance management application
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Finovara&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Finovara&lt;/strong&gt; is a financial management platform designed to help users effectively track
analyze, and optimize their income, expenses, and savings
The application provides a secure, bank-like experience focused on transparency,
financial awareness, and long-term money planning.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🎯 Purpose of the Application&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Finovara aims to support users in making better financial decisions by offering
clear insights into their financial activity and helping them maintain control
over their budgets and savings.&lt;/p&gt;

&lt;p&gt;The platform focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;organizing income and expenses in a structured way&lt;/li&gt;
&lt;li&gt;visualizing financial data through charts and statistics&lt;/li&gt;
&lt;li&gt;supporting saving goals and spending limits&lt;/li&gt;
&lt;li&gt;providing a virtual wallet concept for daily financial management&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Key Features&lt;/h2&gt;

&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;Secure user authentication and authorization&lt;/li&gt;

&lt;li&gt;Income and expense tracking&lt;/li&gt;

&lt;li&gt;Categorization of financial operations&lt;/li&gt;

&lt;li&gt;Interactive charts and financial statistics&lt;/li&gt;

&lt;li&gt;Reports summarizing spending and income trends&lt;/li&gt;

&lt;li&gt;Virtual wallet management&lt;/li&gt;

&lt;li&gt;Savings goals (e.g. piggy banks)&lt;/li&gt;

&lt;li&gt;Spending limits and budget control&lt;/li&gt;

&lt;li&gt;Scalable architecture prepared for future financial…&lt;/li&gt;

&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/M4rc1nek/finovara-backend" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;&lt;/strong&gt;&lt;/p&gt;





</description>
      <category>programming</category>
      <category>testing</category>
      <category>java</category>
      <category>webdev</category>
    </item>
    <item>
      <title>User Notification Settings - Finovara</title>
      <dc:creator>Marcin Parśniak</dc:creator>
      <pubDate>Thu, 05 Mar 2026 22:35:56 +0000</pubDate>
      <link>https://dev.to/m4rc1nek/user-notification-settings-finovara-2358</link>
      <guid>https://dev.to/m4rc1nek/user-notification-settings-finovara-2358</guid>
      <description>&lt;h2&gt;
  
  
  Hello everyone!
&lt;/h2&gt;

&lt;p&gt;In the latest update to &lt;strong&gt;Finovara&lt;/strong&gt;, &lt;br&gt;
I implemented user notification settings.&lt;br&gt;
This feature allows users to decide whether they want to receive &lt;strong&gt;email notifications&lt;/strong&gt; for important account events.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this feature does
&lt;/h2&gt;

&lt;p&gt;Users can now enable or disable notifications for important events happening in their account.&lt;/p&gt;

&lt;p&gt;When notifications are enabled and the user:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;changes their password&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;changes username&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;deleted account&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the system will send an email notification to the address assigned to his account informing his of the change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of mail sending method:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt; &lt;span class="nd"&gt;@Async&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;templatePath&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;MimeMessage&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javaMailSender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createMimeMessage&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;MimeMessageHelper&lt;/span&gt; &lt;span class="n"&gt;helper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MimeMessageHelper&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEmail&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setFrom&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Finovara &amp;lt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;senderAddress&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&amp;gt;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setReplyTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;senderAddress&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSubject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loadTemplate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;templatePath&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;javaMailSender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to send email"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example loadTemplate:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;loadTemplate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;templatePath&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;ClassPathResource&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ClassPathResource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;templatePath&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InputStream&lt;/span&gt; &lt;span class="n"&gt;inputStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInputStream&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readAllBytes&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;StandardCharsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;UTF_8&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{{username}}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{{email}}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to load email template"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Thanks for reading!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I encourage you to take a look at the process of creating the Finovara application at:&lt;/em&gt; &lt;a href="https://github.com/M4rc1nek/finovara-backend" rel="noopener noreferrer"&gt;https://github.com/M4rc1nek/finovara-backend&lt;/a&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>java</category>
      <category>programming</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Moving from Hibernate Auto-DDL to Liquibase - Finovara</title>
      <dc:creator>Marcin Parśniak</dc:creator>
      <pubDate>Tue, 03 Mar 2026 18:54:06 +0000</pubDate>
      <link>https://dev.to/m4rc1nek/moving-from-hibernate-auto-ddl-to-liquibase-finovara-aoc</link>
      <guid>https://dev.to/m4rc1nek/moving-from-hibernate-auto-ddl-to-liquibase-finovara-aoc</guid>
      <description>&lt;h2&gt;
  
  
  Hello!
&lt;/h2&gt;

&lt;p&gt;For a long time in my Spring Boot project, I relied on&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;hibernate.ddl-auto = update&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It was simple.&lt;br&gt;
Change an entity → restart the app → the database updates itself.&lt;/p&gt;

&lt;p&gt;But when &lt;strong&gt;the project grew to 18 entities&lt;/strong&gt;, I realized something important:&lt;/p&gt;

&lt;p&gt;I had zero control over my database history.&lt;/p&gt;

&lt;p&gt;So I decided to switch to &lt;strong&gt;Liquibase&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Changed
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Disabled Hibernate auto schema updates&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jpa&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hibernate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ddl-auto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;From that moment, &lt;strong&gt;Hibernate stopped modifying the database structure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Enabled Liquibase&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;liquibase&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;change-log&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;classpath:db/changelog/changelog.xml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;3. Added simple changeSets&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;changeSet&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"1-create-users"&lt;/span&gt; &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"Marcin Parśniak"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;createTable&lt;/span&gt; &lt;span class="na"&gt;tableName=&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"BIGINT"&lt;/span&gt; &lt;span class="na"&gt;autoIncrement=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;constraints&lt;/span&gt; &lt;span class="na"&gt;primaryKey=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;nullable=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/column&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"VARCHAR(255)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;constraints&lt;/span&gt; &lt;span class="na"&gt;nullable=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;unique=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/column&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"VARCHAR(255)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;constraints&lt;/span&gt; &lt;span class="na"&gt;nullable=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/column&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"VARCHAR(255)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;constraints&lt;/span&gt; &lt;span class="na"&gt;nullable=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;unique=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/column&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"created_at"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"TIMESTAMP"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"profile_image_path"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"VARCHAR(500)"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/createTable&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/changeSet&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Now Liquibase manages the schema.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you want to see how im doing it, check out my github profile:&lt;/strong&gt; 

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/M4rc1nek" rel="noopener noreferrer"&gt;
        M4rc1nek
      &lt;/a&gt; / &lt;a href="https://github.com/M4rc1nek/finovara-backend" rel="noopener noreferrer"&gt;
        finovara-backend
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Backend service for a personal finance management application
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Finovara&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Finovara&lt;/strong&gt; is a financial management platform designed to help users effectively track
analyze, and optimize their income, expenses, and savings
The application provides a secure, bank-like experience focused on transparency,
financial awareness, and long-term money planning.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🎯 Purpose of the Application&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Finovara aims to support users in making better financial decisions by offering
clear insights into their financial activity and helping them maintain control
over their budgets and savings.&lt;/p&gt;
&lt;p&gt;The platform focuses on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;organizing income and expenses in a structured way&lt;/li&gt;
&lt;li&gt;visualizing financial data through charts and statistics&lt;/li&gt;
&lt;li&gt;supporting saving goals and spending limits&lt;/li&gt;
&lt;li&gt;providing a virtual wallet concept for daily financial management&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Key Features&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Secure user authentication and authorization&lt;/li&gt;
&lt;li&gt;Income and expense tracking&lt;/li&gt;
&lt;li&gt;Categorization of financial operations&lt;/li&gt;
&lt;li&gt;Interactive charts and financial statistics&lt;/li&gt;
&lt;li&gt;Reports summarizing spending and income trends&lt;/li&gt;
&lt;li&gt;Virtual wallet management&lt;/li&gt;
&lt;li&gt;Savings goals (e.g. piggy banks)&lt;/li&gt;
&lt;li&gt;Spending limits and budget control&lt;/li&gt;
&lt;li&gt;Scalable architecture prepared for future financial…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/M4rc1nek/finovara-backend" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




</description>
      <category>java</category>
      <category>programming</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Reccuring Revenue - Finovara</title>
      <dc:creator>Marcin Parśniak</dc:creator>
      <pubDate>Mon, 02 Mar 2026 21:27:00 +0000</pubDate>
      <link>https://dev.to/m4rc1nek/reccuring-revenue-finovara-4apd</link>
      <guid>https://dev.to/m4rc1nek/reccuring-revenue-finovara-4apd</guid>
      <description>&lt;h2&gt;
  
  
  Hello everyone!
&lt;/h2&gt;

&lt;p&gt;A while ago, I added a new setting to the Finovara app.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What did I add?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I added a user setting for &lt;strong&gt;recurring revenue&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The user specifies the:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;recurring revenue amount&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;revenue category&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;start date&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;frequency of revenue generation (daily, weekly, monthly)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How does it work?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;scheduler runs cron daily&lt;/strong&gt; (currently every minute as a test, but will run daily at midnight).&lt;/p&gt;

&lt;p&gt;The cron runs and checks &lt;strong&gt;if the revenue generation date matches&lt;/strong&gt;.&lt;br&gt;
If so, we check the condition in the code.&lt;br&gt;
If it matches, &lt;strong&gt;a recurring revenue is created&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is what an example implementation in a processor looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;RevenueSettings&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRevenueSettings&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isRecurringRevenuesEnable&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNextExecutionDate&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNextExecutionDate&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isAfter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;)){&lt;/span&gt;
                &lt;span class="n"&gt;createRecurringRevenue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRecurringStrategy&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;DAILY&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setNextExecutionDate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNextExecutionDate&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;plusDays&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;WEEKLY&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setNextExecutionDate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNextExecutionDate&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;plusWeeks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;MONTHLY&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setNextExecutionDate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNextExecutionDate&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;plusMonths&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>java</category>
      <category>programming</category>
      <category>backend</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Finovara - Piggy banks activity logs.</title>
      <dc:creator>Marcin Parśniak</dc:creator>
      <pubDate>Fri, 27 Feb 2026 21:31:35 +0000</pubDate>
      <link>https://dev.to/m4rc1nek/finovara-piggy-banks-activity-logs-3pio</link>
      <guid>https://dev.to/m4rc1nek/finovara-piggy-banks-activity-logs-3pio</guid>
      <description>&lt;p&gt;&lt;strong&gt;Hey everyone&lt;/strong&gt; 🙂&lt;/p&gt;

&lt;p&gt;I’ve been working on another improvement in my &lt;strong&gt;Finovara&lt;/strong&gt; project — this time around Piggy Banks.&lt;/p&gt;

&lt;p&gt;Recently I added something I called &lt;strong&gt;Activity tracking&lt;/strong&gt; for savings. The idea was simple: when people save money, they should clearly see what actually happened in their piggy banks.&lt;/p&gt;

&lt;p&gt;So now, whenever a user:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;• puts money into a piggy bank&lt;br&gt;
• withdraws funds&lt;br&gt;
• creates a new piggy bank&lt;br&gt;
• deletes one&lt;br&gt;
• or performs any savings-related action&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;it all gets recorded in their activity history.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each entry shows what happened, when it happened, and the amount involved. Nothing fancy, just clear visibility so users don’t have to guess where their money moved.&lt;/p&gt;




&lt;p&gt;While working on this, I also &lt;strong&gt;cleaned up something on the backend side&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Previously, every saving feature had to be saved separately. It worked, but it felt messy and required multiple API calls.&lt;/p&gt;

&lt;p&gt;So I introduced &lt;strong&gt;a single endpoint&lt;/strong&gt; that lets the frontend save all piggy bank settings in one request.&lt;/p&gt;

&lt;p&gt;Now it handles things like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;• automatic savings rules&lt;br&gt;
• transaction round-ups&lt;br&gt;
• goal completion behavior&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The backend simply &lt;strong&gt;receives one DTO with all sections&lt;/strong&gt; and passes them to the right services internally.&lt;/p&gt;

&lt;p&gt;It made the whole flow much simpler, both for the API and for the frontend.&lt;/p&gt;

&lt;p&gt;Thanks for reading! :D&lt;/p&gt;

</description>
      <category>programming</category>
      <category>backend</category>
      <category>java</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Finovara - Limits activity logs.</title>
      <dc:creator>Marcin Parśniak</dc:creator>
      <pubDate>Wed, 25 Feb 2026 20:13:46 +0000</pubDate>
      <link>https://dev.to/m4rc1nek/finovara-limits-activity-logs-329c</link>
      <guid>https://dev.to/m4rc1nek/finovara-limits-activity-logs-329c</guid>
      <description>&lt;p&gt;&lt;strong&gt;Hi everyone,&lt;/strong&gt;&lt;br&gt;
In the latest update of my &lt;strong&gt;Finovara&lt;/strong&gt; project, I added more logs for user activities about limits.&lt;br&gt;
&lt;strong&gt;How ​​does it work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As soon as a user:&lt;br&gt;
&lt;strong&gt;• Adds a limit&lt;br&gt;
• Removes a limit&lt;br&gt;
• Edits a limit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They can see all their account activities.&lt;/p&gt;

&lt;p&gt;A sample &lt;strong&gt;JSON&lt;/strong&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"limitActivityType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ADDED_LIMIT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"limitType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"DAILY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"previousAmount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-02-25T14:30:00"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Records are &lt;strong&gt;deleted&lt;/strong&gt; from the database every month thanks to the &lt;strong&gt;scheduler&lt;/strong&gt; and &lt;strong&gt;cron&lt;/strong&gt; &lt;strong&gt;job&lt;/strong&gt; set in the .yml file.&lt;br&gt;
Here's the link to the commit related to this update: &lt;strong&gt;&lt;a href="https://github.com/M4rc1nek/finovara-backend/commit/ce42ab006d46d0c309d3806ddf83498065cbd3ea" rel="noopener noreferrer"&gt;https://github.com/M4rc1nek/finovara-backend/commit/ce42ab006d46d0c309d3806ddf83498065cbd3ea&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>showdev</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Building Activity Logs for Expenses and Revenue in Finovara</title>
      <dc:creator>Marcin Parśniak</dc:creator>
      <pubDate>Tue, 24 Feb 2026 08:52:38 +0000</pubDate>
      <link>https://dev.to/m4rc1nek/building-activity-logs-for-expenses-and-revenue-in-finovara-17hj</link>
      <guid>https://dev.to/m4rc1nek/building-activity-logs-for-expenses-and-revenue-in-finovara-17hj</guid>
      <description>&lt;p&gt;&lt;strong&gt;Hi everyone,&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First of all, I would like to invite everyone to my github: &lt;a href="https://github.com/M4rc1nek/finovara-backend" rel="noopener noreferrer"&gt;https://github.com/M4rc1nek/finovara-backend&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
So,&lt;/p&gt;

&lt;p&gt;I’ve just finished working on a new feature in my project &lt;strong&gt;Finovara&lt;/strong&gt;. I added a full activity log system that tracks changes for both expenses and revenue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Users should always be able to see what happened to their data, what changed, when it changed, and what the previous values were.&lt;/p&gt;

&lt;p&gt;That’s why I decided to implement an audit-style activity history for all financial operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the system tracks
&lt;/h2&gt;

&lt;p&gt;The activity logs now record actions for both expenses and revenue. It includes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;• adding a record&lt;br&gt;
• editing a record&lt;br&gt;
• deleting a record&lt;br&gt;
• saving previous values whenever something is updated&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Logs for expenses and revenue work in exactly the same way. They follow the same structure and logic, just applied to different types of financial data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a log entry contains
&lt;/h2&gt;

&lt;p&gt;Each activity record stores:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;• type of operation&lt;br&gt;
• amount&lt;br&gt;
• category&lt;br&gt;
• date&lt;br&gt;
• previous amount when edited&lt;br&gt;
• previous category when edited&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because of this, when a value changes, the user can clearly see both the old and the new data.&lt;/p&gt;

&lt;p&gt;How it works under the hood&lt;/p&gt;

&lt;p&gt;All logs are stored in dedicated tables that act as an audit trail. Whenever an expense or revenue operation happens, the service layer automatically creates an activity entry. This keeps the logging fully transparent and doesn’t clutter the business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sorting
&lt;/h2&gt;

&lt;p&gt;Users can sort their activity history by newest, oldest, highest amount, or lowest amount.&lt;/p&gt;

&lt;p&gt;I implemented this using Spring Data sorting. The frontend sends the selected option, and the backend maps it to the proper configuration so the database returns already sorted results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic cleanup
&lt;/h2&gt;

&lt;p&gt;I also added a &lt;strong&gt;scheduler&lt;/strong&gt; to prevent the tables from growing forever. Every six months it automatically removes old activity logs. This keeps the system clean while still preserving a long enough history window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;This makes Finovara feel much closer to a &lt;strong&gt;real financial system&lt;/strong&gt;. Users get full visibility into their financial history and can always understand what happened to their data.&lt;/p&gt;

&lt;p&gt;It also creates a solid foundation for future features like security activity tracking, full account history logs, and advanced auditing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More updates coming soon.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>java</category>
      <category>springboot</category>
    </item>
    <item>
      <title>My programming journey from age 10 to building a real backend system</title>
      <dc:creator>Marcin Parśniak</dc:creator>
      <pubDate>Sun, 22 Feb 2026 19:45:09 +0000</pubDate>
      <link>https://dev.to/m4rc1nek/my-programming-journey-from-age-10-to-building-a-real-backend-system-2eip</link>
      <guid>https://dev.to/m4rc1nek/my-programming-journey-from-age-10-to-building-a-real-backend-system-2eip</guid>
      <description>&lt;p&gt;&lt;strong&gt;Hi everyone&lt;/strong&gt;,&lt;/p&gt;

&lt;p&gt;My name is &lt;strong&gt;Marcin&lt;/strong&gt;, I’m &lt;strong&gt;16 years old&lt;/strong&gt;, and programming has been a part of my life for many years.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it started
&lt;/h2&gt;

&lt;p&gt;I began my journey at the age of &lt;strong&gt;10&lt;/strong&gt;, learning &lt;strong&gt;C# completely on my own&lt;/strong&gt;.&lt;br&gt;
At first, I built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simple &lt;strong&gt;console programs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;small &lt;strong&gt;logic games&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;basic &lt;strong&gt;interactive projects&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was the moment I realized I genuinely enjoyed &lt;strong&gt;creating things from nothing using code&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exploring technology
&lt;/h2&gt;

&lt;p&gt;As I grew older, I started exploring different areas of tech.&lt;br&gt;
For a while, I focused on &lt;strong&gt;game development&lt;/strong&gt;, building projects in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Godot (GDScript)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unity (C#)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unreal Engine (Blueprints)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Through this, I learned not only programming, but also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;problem-solving&lt;/li&gt;
&lt;li&gt;logical thinking&lt;/li&gt;
&lt;li&gt;how complex systems are structured&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Beyond coding — understanding computers
&lt;/h2&gt;

&lt;p&gt;At the same time, I became interested in the broader technical side of computers.&lt;/p&gt;

&lt;p&gt;I spent time learning about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;computer hardware&lt;/li&gt;
&lt;li&gt;how operating systems work internally&lt;/li&gt;
&lt;li&gt;networking fundamentals&lt;/li&gt;
&lt;li&gt;basic cybersecurity concepts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was always curious about &lt;strong&gt;how systems really function under the hood&lt;/strong&gt;, not just how to use them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing a direction
&lt;/h2&gt;

&lt;p&gt;Around the age of &lt;strong&gt;15&lt;/strong&gt;, I decided to focus fully on &lt;strong&gt;backend development&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Since then, I’ve been specializing in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Java&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring ecosystem&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;backend architecture&lt;/li&gt;
&lt;li&gt;building real-world applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What interests me most is &lt;strong&gt;designing systems&lt;/strong&gt;, implementing business logic, and understanding how everything connects together.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I’m working on now
&lt;/h2&gt;

&lt;p&gt;Currently, I’m developing my biggest project so far — &lt;strong&gt;Finovara&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It’s a &lt;strong&gt;personal finance management backend system&lt;/strong&gt;, similar to a virtual banking logic platform, which I’m designing and building entirely on my own.&lt;/p&gt;

&lt;p&gt;This project allows me to combine everything I’ve been learning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;system architecture&lt;/li&gt;
&lt;li&gt;backend engineering&lt;/li&gt;
&lt;li&gt;security thinking&lt;/li&gt;
&lt;li&gt;real-world problem solving&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Constant learning
&lt;/h2&gt;

&lt;p&gt;I spend a lot of time &lt;strong&gt;learning, experimenting, and improving every day&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Programming isn’t just something I study —&lt;br&gt;
it’s something I naturally &lt;strong&gt;enjoy doing in my free time&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you’re interested
&lt;/h2&gt;

&lt;p&gt;If you’d like to see what I’m working on, feel free to check my &lt;strong&gt;GitHub&lt;/strong&gt;, especially my main project &lt;strong&gt;Finovara&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>java</category>
      <category>spring</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
