<?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: Yadrs</title>
    <description>The latest articles on DEV Community by Yadrs (@yadrs).</description>
    <link>https://dev.to/yadrs</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3976233%2Fa023635b-6453-4ca7-802e-8eefcc0b0b81.png</url>
      <title>DEV Community: Yadrs</title>
      <link>https://dev.to/yadrs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yadrs"/>
    <language>en</language>
    <item>
      <title>AWS EC2 vs ECS for Spring Boot Deployment — When Should You Use Which?</title>
      <dc:creator>Yadrs</dc:creator>
      <pubDate>Mon, 22 Jun 2026 12:44:29 +0000</pubDate>
      <link>https://dev.to/yadrs/ec2-vs-ecs-for-spring-boot-deployment-when-should-you-use-which-30ld</link>
      <guid>https://dev.to/yadrs/ec2-vs-ecs-for-spring-boot-deployment-when-should-you-use-which-30ld</guid>
      <description>&lt;p&gt;Building a Spring Boot application is only half the journey.&lt;/p&gt;

&lt;p&gt;At some point, every backend project reaches the next question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Where should this application actually run?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are deploying on AWS, two common options are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon EC2&lt;/li&gt;
&lt;li&gt;Amazon ECS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both can run the same Dockerized Spring Boot application.&lt;/p&gt;

&lt;p&gt;But they are not the same kind of deployment model.&lt;/p&gt;

&lt;p&gt;EC2 gives you a server.&lt;/p&gt;

&lt;p&gt;ECS gives you container orchestration.&lt;/p&gt;

&lt;p&gt;Choosing between them is less about which one is “better” and more about which one fits the stage of your application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Starting with EC2
&lt;/h2&gt;

&lt;p&gt;EC2 is the most direct way to run a Spring Boot application on AWS.&lt;/p&gt;

&lt;p&gt;You create a virtual machine, install what you need, and run your application.&lt;/p&gt;

&lt;p&gt;For a Dockerized Spring Boot app, the flow often looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub Actions
      ↓
SSH into EC2
      ↓
Pull latest code or image
      ↓
Build / run Docker container
      ↓
Spring Boot app runs on EC2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mental model is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One server
One Docker container
One Spring Boot application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That simplicity is useful.&lt;/p&gt;

&lt;p&gt;Especially when you are launching an MVP, a freelancer project, an internal tool, or your first production version.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why EC2 Works Well for Many Spring Boot Apps
&lt;/h2&gt;

&lt;p&gt;EC2 is not outdated just because newer deployment platforms exist.&lt;/p&gt;

&lt;p&gt;For many applications, EC2 is enough.&lt;/p&gt;

&lt;p&gt;It works well when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;traffic is predictable&lt;/li&gt;
&lt;li&gt;the application is small or medium-sized&lt;/li&gt;
&lt;li&gt;you want full control over the server&lt;/li&gt;
&lt;li&gt;you want simple debugging through SSH&lt;/li&gt;
&lt;li&gt;you do not need multiple replicas yet&lt;/li&gt;
&lt;li&gt;you want to keep infrastructure easy to understand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common setup might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
Dockerfile
Docker Compose
GitHub Actions
EC2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That setup can be production-ready for many early-stage apps.&lt;/p&gt;

&lt;p&gt;It gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a real deployment target&lt;/li&gt;
&lt;li&gt;repeatable Docker builds&lt;/li&gt;
&lt;li&gt;automated deployment through GitHub Actions&lt;/li&gt;
&lt;li&gt;simple logs&lt;/li&gt;
&lt;li&gt;direct server access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a solo developer or small team, that matters.&lt;/p&gt;

&lt;p&gt;You can understand the entire deployment flow without needing a full container orchestration platform.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tradeoff With EC2
&lt;/h2&gt;

&lt;p&gt;The tradeoff is that you own more of the server.&lt;/p&gt;

&lt;p&gt;With EC2, you usually need to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installing Docker&lt;/li&gt;
&lt;li&gt;OS updates&lt;/li&gt;
&lt;li&gt;disk space&lt;/li&gt;
&lt;li&gt;server monitoring&lt;/li&gt;
&lt;li&gt;restart behavior&lt;/li&gt;
&lt;li&gt;SSH access&lt;/li&gt;
&lt;li&gt;security groups&lt;/li&gt;
&lt;li&gt;manual scaling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, this is manageable.&lt;/p&gt;

&lt;p&gt;But over time, deployment scripts can grow.&lt;/p&gt;

&lt;p&gt;You may start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./deploy.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then later need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;zero-downtime deploys
multiple instances
load balancers
auto scaling
image registries
rolling deployments
health-based restarts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that point, EC2 can still work, but you may start rebuilding orchestration manually.&lt;/p&gt;

&lt;p&gt;That is usually when ECS becomes more attractive.&lt;/p&gt;




&lt;h2&gt;
  
  
  What ECS Changes
&lt;/h2&gt;

&lt;p&gt;Amazon ECS is AWS's container orchestration service.&lt;/p&gt;

&lt;p&gt;Instead of thinking primarily about servers, you think about containers.&lt;/p&gt;

&lt;p&gt;With ECS, you describe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Docker image&lt;/li&gt;
&lt;li&gt;CPU and memory&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;li&gt;secrets&lt;/li&gt;
&lt;li&gt;desired number of running tasks&lt;/li&gt;
&lt;li&gt;load balancer configuration&lt;/li&gt;
&lt;li&gt;deployment strategy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then ECS keeps those containers running.&lt;/p&gt;

&lt;p&gt;A simplified flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub Actions
      ↓
Build Docker image
      ↓
Push image to registry
      ↓
Update ECS service
      ↓
ECS runs Spring Boot tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of manually SSHing into a server, ECS manages container placement and restarts.&lt;/p&gt;




&lt;h2&gt;
  
  
  What ECS Gives You
&lt;/h2&gt;

&lt;p&gt;ECS becomes useful when your application needs more operational maturity.&lt;/p&gt;

&lt;p&gt;It gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;container orchestration&lt;/li&gt;
&lt;li&gt;desired task count&lt;/li&gt;
&lt;li&gt;rolling deployments&lt;/li&gt;
&lt;li&gt;automatic task replacement&lt;/li&gt;
&lt;li&gt;load balancer integration&lt;/li&gt;
&lt;li&gt;easier horizontal scaling&lt;/li&gt;
&lt;li&gt;cleaner separation between image build and runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, instead of running one container manually on one EC2 instance, you can say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run 2 copies of this Spring Boot container.
Keep them healthy.
Replace failed tasks.
Deploy new versions gradually.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a different level of infrastructure support.&lt;/p&gt;




&lt;h2&gt;
  
  
  EC2 vs ECS: Practical Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;EC2&lt;/th&gt;
&lt;th&gt;ECS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mental model&lt;/td&gt;
&lt;td&gt;Server-based&lt;/td&gt;
&lt;td&gt;Container-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup complexity&lt;/td&gt;
&lt;td&gt;Lower&lt;/td&gt;
&lt;td&gt;Higher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSH access&lt;/td&gt;
&lt;td&gt;Direct&lt;/td&gt;
&lt;td&gt;Usually not needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker support&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Mostly manual&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rolling deploys&lt;/td&gt;
&lt;td&gt;Custom scripts&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server maintenance&lt;/td&gt;
&lt;td&gt;You manage more&lt;/td&gt;
&lt;td&gt;AWS manages more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Good for MVPs&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Good for multiple services&lt;/td&gt;
&lt;td&gt;Possible&lt;/td&gt;
&lt;td&gt;Better&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operational maturity&lt;/td&gt;
&lt;td&gt;Simple&lt;/td&gt;
&lt;td&gt;Stronger&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cost is also a factor. EC2 gives you a predictable instance cost. ECS on Fargate charges per vCPU and memory per second — which can be cheaper at low usage but higher at sustained load. Neither is always cheaper. It depends on your traffic pattern.&lt;/p&gt;

&lt;p&gt;Neither option is always better.&lt;/p&gt;

&lt;p&gt;They solve different problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  When EC2 Is the Better Choice
&lt;/h2&gt;

&lt;p&gt;EC2 is usually a better starting point when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you are building your first production deployment&lt;/li&gt;
&lt;li&gt;you want fewer AWS concepts to manage&lt;/li&gt;
&lt;li&gt;you have one backend service&lt;/li&gt;
&lt;li&gt;you are deploying an MVP&lt;/li&gt;
&lt;li&gt;you want to debug easily with SSH&lt;/li&gt;
&lt;li&gt;traffic is low or predictable&lt;/li&gt;
&lt;li&gt;you are optimizing for speed of launch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many solo builders, freelancers, and early-stage products, EC2 is a practical choice.&lt;/p&gt;

&lt;p&gt;A simple deployment that works is better than a sophisticated deployment that slows you down.&lt;/p&gt;




&lt;h2&gt;
  
  
  When ECS Starts Making More Sense
&lt;/h2&gt;

&lt;p&gt;ECS starts making sense when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you have multiple backend services&lt;/li&gt;
&lt;li&gt;deployments are frequent&lt;/li&gt;
&lt;li&gt;you need multiple running containers&lt;/li&gt;
&lt;li&gt;you want rolling deployments&lt;/li&gt;
&lt;li&gt;you want easier horizontal scaling&lt;/li&gt;
&lt;li&gt;you want less manual server management&lt;/li&gt;
&lt;li&gt;you are already building and pushing Docker images&lt;/li&gt;
&lt;li&gt;your deployment scripts are becoming hard to maintain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key signal is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Containers are popular, so I should use ECS.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The better signal is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I am starting to manage container orchestration manually.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is when ECS can reduce operational work.&lt;/p&gt;




&lt;h2&gt;
  
  
  What About Kubernetes?
&lt;/h2&gt;

&lt;p&gt;Kubernetes is powerful.&lt;/p&gt;

&lt;p&gt;But many Spring Boot applications do not need it on day one.&lt;/p&gt;

&lt;p&gt;A practical progression often looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot JAR
      ↓
Dockerized Spring Boot app
      ↓
EC2 deployment
      ↓
ECS service
      ↓
Kubernetes, if needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Skipping directly to Kubernetes can add complexity before the application needs it.&lt;/p&gt;

&lt;p&gt;For many teams, ECS is already a big step up from manually managing containers on EC2.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Question: What Stage Is Your App In?
&lt;/h2&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should I use EC2 or ECS?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What does this application need right now?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you need a simple, understandable production deployment, EC2 is often enough.&lt;/p&gt;

&lt;p&gt;If you need orchestration, scaling, and managed container deployments, ECS becomes more valuable.&lt;/p&gt;

&lt;p&gt;The same Spring Boot application can move through both stages.&lt;/p&gt;

&lt;p&gt;That is why Docker is such a useful foundation.&lt;/p&gt;

&lt;p&gt;Once your app is containerized, you can start on EC2 and move toward ECS later if the operational needs grow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why SpringGen Starts With EC2 Deployment Automation
&lt;/h2&gt;

&lt;p&gt;SpringGen Pro currently generates the full EC2 deployment path —&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate Spring Boot project
      ↓
Dockerize it
      ↓
Add production config
      ↓
Generate GitHub Actions CI/CD
      ↓
Deploy to AWS EC2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That starting point is intentional. Most developers need a reliable, understandable production deployment before they need orchestration. EC2 delivers that. The same Docker foundation moves to ECS when the application grows.&lt;/p&gt;

&lt;p&gt;SpringGen is designed to help developers skip repetitive setup and get to the real application faster.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://app.springgen.dev" rel="noopener noreferrer"&gt;https://app.springgen.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://github.com/springgen-dev/springgen" rel="noopener noreferrer"&gt;https://github.com/springgen-dev/springgen&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;EC2 is simple infrastructure.&lt;/p&gt;

&lt;p&gt;ECS is container orchestration.&lt;/p&gt;

&lt;p&gt;Both are useful.&lt;/p&gt;

&lt;p&gt;The mistake is not choosing EC2 or ECS.&lt;/p&gt;

&lt;p&gt;The mistake is choosing complexity before your application needs it.&lt;/p&gt;

&lt;p&gt;Start with the deployment model that helps you ship.&lt;/p&gt;

&lt;p&gt;Then evolve when the operational pain becomes real.&lt;/p&gt;

&lt;p&gt;What do you usually choose for Spring Boot deployments — EC2, ECS, Kubernetes, or something else?&lt;/p&gt;

</description>
      <category>aws</category>
      <category>springboot</category>
      <category>java</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Structure Spring Boot Projects Beyond Spring Initializr</title>
      <dc:creator>Yadrs</dc:creator>
      <pubDate>Sat, 20 Jun 2026 13:03:47 +0000</pubDate>
      <link>https://dev.to/yadrs/how-to-structure-spring-boot-projects-beyond-spring-initializr-fac</link>
      <guid>https://dev.to/yadrs/how-to-structure-spring-boot-projects-beyond-spring-initializr-fac</guid>
      <description>&lt;p&gt;Spring Initializr is one of the best tools in the Java ecosystem.&lt;/p&gt;

&lt;p&gt;It solves the first problem every Spring Boot developer has:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do I create a working Spring Boot application quickly?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You select dependencies, choose your Java version, generate the ZIP, and you have a running application in seconds.&lt;/p&gt;

&lt;p&gt;But after that first commit, every team faces the same question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How should we actually structure this application?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because Spring Initializr gives you a starting point — not a production architecture.&lt;/p&gt;

&lt;p&gt;Let's look at how many real-world Spring Boot projects evolve beyond the initial scaffold.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Default Spring Initializr Structure
&lt;/h2&gt;

&lt;p&gt;A freshly generated project usually looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/main/java/com/example/app

├── Application.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And technically, that is enough.&lt;/p&gt;

&lt;p&gt;You can start adding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;controllers
services
repositories
entities
configuration
security
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But Spring does not enforce where those things go.&lt;/p&gt;

&lt;p&gt;That flexibility is powerful.&lt;/p&gt;

&lt;p&gt;It also means every new project requires architecture decisions.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Separate Controllers From Business Logic
&lt;/h3&gt;

&lt;p&gt;A common early mistake is putting too much logic inside controllers.&lt;/p&gt;

&lt;p&gt;Example:&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;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@RequestBody&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="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;validateUser&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;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCreatedAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;sendWelcomeEmail&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userRepository&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;user&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;It works.&lt;/p&gt;

&lt;p&gt;But controllers quickly become responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request handling&lt;/li&gt;
&lt;li&gt;validation&lt;/li&gt;
&lt;li&gt;business rules&lt;/li&gt;
&lt;li&gt;persistence logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, keep controllers thin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller
     |
     v
Service
     |
     v
Repository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&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;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt; &lt;span class="n"&gt;userService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;UserResponse&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;CreateUserRequest&lt;/span&gt; &lt;span class="n"&gt;request&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;userService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&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;The controller handles HTTP.&lt;/p&gt;

&lt;p&gt;The service owns business logic.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Add DTOs Instead of Exposing Entities
&lt;/h3&gt;

&lt;p&gt;A common shortcut:&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;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/{id}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;getUser&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;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&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;The problem?&lt;/p&gt;

&lt;p&gt;Your database model becomes your API contract.&lt;/p&gt;

&lt;p&gt;Changing a column, renaming a field, or adding internal data can accidentally change what your API exposes.&lt;/p&gt;

&lt;p&gt;Later changes become risky.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Entity
  |
Mapper
  |
DTO
  |
API Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&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="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;UserResponse&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;id&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;safer APIs&lt;/li&gt;
&lt;li&gt;easier versioning&lt;/li&gt;
&lt;li&gt;prevents leaking internal fields&lt;/li&gt;
&lt;li&gt;separates persistence from contracts&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Create a Dedicated Exception Layer
&lt;/h3&gt;

&lt;p&gt;Without centralized exception handling:&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;try&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;appears everywhere.&lt;/p&gt;

&lt;p&gt;Spring provides a cleaner pattern:&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;@RestControllerAdvice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GlobalExceptionHandler&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&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="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handle&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;ex&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="nc"&gt;ResponseEntity&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&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;



&lt;p&gt;Note: A real implementation usually handles specific exception types with appropriate status codes — 404 for not found, 400 for validation errors, 403 for access denied.&lt;/p&gt;

&lt;p&gt;Now errors are handled consistently.&lt;/p&gt;

&lt;p&gt;Your controllers stay focused.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Keep Configuration Isolated
&lt;/h3&gt;

&lt;p&gt;Production applications eventually need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;security configuration&lt;/li&gt;
&lt;li&gt;CORS rules&lt;/li&gt;
&lt;li&gt;authentication filters&lt;/li&gt;
&lt;li&gt;database configuration&lt;/li&gt;
&lt;li&gt;external service clients&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid scattering configuration everywhere.&lt;/p&gt;

&lt;p&gt;Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config/

├── SecurityConfig.java
├── CorsConfig.java
├── AppConfig.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It keeps infrastructure concerns separate from business code.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Validate Requests at the Boundary
&lt;/h3&gt;

&lt;p&gt;Do not let invalid data travel deep into your application.&lt;/p&gt;

&lt;p&gt;Example:&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="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;CreateUserRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;

    &lt;span class="nd"&gt;@NotBlank&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;

    &lt;span class="nd"&gt;@Email&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validation keeps services focused on business rules instead of checking basic request correctness.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Organize Security Separately
&lt;/h3&gt;

&lt;p&gt;Authentication grows quickly.&lt;/p&gt;

&lt;p&gt;A basic project may start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SecurityConfig.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then later needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;security/

├── JwtService.java
├── JwtAuthenticationFilter.java
├── OAuth2SuccessHandler.java
├── RefreshTokenService.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping security isolated makes the project easier to maintain.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Add Environment Separation Early
&lt;/h3&gt;

&lt;p&gt;Many projects start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eventually they need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application.yml

application-dev.yml

application-prod.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Local:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;localhost database
debug logging
local secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;environment variables
secure configs
optimized logging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Separating environments early prevents painful migrations later.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. Choose Layer-Based vs Feature-Based Structure
&lt;/h3&gt;

&lt;p&gt;Some teams prefer organizing by feature instead of layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/main/java/com/company/app

├── user/
│   ├── UserController.java
│   ├── UserService.java
│   ├── UserRepository.java
│   └── UserResponse.java
├── order/
│   ├── OrderController.java
│   └── OrderService.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feature-based structure scales better for larger applications where each domain grows independently. &lt;/p&gt;

&lt;p&gt;Layered structure is simpler for smaller applications and easier for teams new to the codebase.&lt;/p&gt;




&lt;h3&gt;
  
  
  9. A More Production-Friendly Structure
&lt;/h3&gt;

&lt;p&gt;A common structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/main/java/com/company/app

├── controller
├── service
├── repository
├── entity
├── dto
├── mapper
├── exception
├── config
├── security
└── Application.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not the only correct structure.&lt;/p&gt;

&lt;p&gt;But it gives teams a predictable foundation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Spring Boot Structure Should Grow With Your Application
&lt;/h2&gt;

&lt;p&gt;Not every project needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes&lt;/li&gt;
&lt;li&gt;microservices&lt;/li&gt;
&lt;li&gt;complex architecture&lt;/li&gt;
&lt;li&gt;dozens of modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Starting simple is good.&lt;/p&gt;

&lt;p&gt;The goal is not adding folders.&lt;/p&gt;

&lt;p&gt;The goal is separating responsibilities.&lt;/p&gt;

&lt;p&gt;A good Spring Boot foundation should make future changes easier, not harder.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automating the Repeated Setup
&lt;/h2&gt;

&lt;p&gt;Many teams eventually create internal templates or starter repositories to standardize this setup.&lt;/p&gt;

&lt;p&gt;That repeated setup is what SpringGen is designed to solve.&lt;/p&gt;

&lt;p&gt;SpringGen generates Spring Boot project foundations with standard structure, authentication, database configuration, Docker, CI/CD, and deployment files included — so development starts at business logic, not boilerplate.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://app.springgen.dev" rel="noopener noreferrer"&gt;https://app.springgen.dev&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;What structure do you usually prefer for Spring Boot projects — layered, feature-based, or something else?&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>spring</category>
      <category>java</category>
      <category>backend</category>
    </item>
    <item>
      <title>Dockerizing Spring Boot for Local and Production Environments — The Setup You’ll Rebuild Every Time</title>
      <dc:creator>Yadrs</dc:creator>
      <pubDate>Mon, 15 Jun 2026 13:32:14 +0000</pubDate>
      <link>https://dev.to/yadrs/dockerizing-spring-boot-for-local-and-production-environments-the-setup-youll-rebuild-every-time-5dn7</link>
      <guid>https://dev.to/yadrs/dockerizing-spring-boot-for-local-and-production-environments-the-setup-youll-rebuild-every-time-5dn7</guid>
      <description>&lt;p&gt;Running a Spring Boot application locally is straightforward.&lt;/p&gt;

&lt;p&gt;But production introduces different requirements.&lt;/p&gt;

&lt;p&gt;A real deployment usually needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;consistent runtime environments&lt;/li&gt;
&lt;li&gt;database containers for local development&lt;/li&gt;
&lt;li&gt;smaller production images&lt;/li&gt;
&lt;li&gt;environment-based configuration&lt;/li&gt;
&lt;li&gt;faster rebuilds&lt;/li&gt;
&lt;li&gt;secure containers&lt;/li&gt;
&lt;li&gt;repeatable deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where Docker becomes a common part of modern Spring Boot projects.&lt;/p&gt;

&lt;p&gt;Docker is not just about putting a JAR file inside a container.&lt;/p&gt;

&lt;p&gt;The goal is creating a predictable way to build, run, and deploy your application.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Basic Docker Approach
&lt;/h2&gt;

&lt;p&gt;The simplest Spring Boot Docker setup usually looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Copy project
       ↓
Build application
       ↓
Run JAR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A basic image might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install Java&lt;/li&gt;
&lt;li&gt;copy the source code&lt;/li&gt;
&lt;li&gt;build the application&lt;/li&gt;
&lt;li&gt;start the application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This works.&lt;/p&gt;

&lt;p&gt;But it also introduces problems:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Large images

Slow rebuilds

Build tools inside production containers

Unnecessary files copied into images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For learning, that approach is fine.&lt;/p&gt;

&lt;p&gt;For long-running applications, there are better patterns.&lt;/p&gt;


&lt;h2&gt;
  
  
  Multi-Stage Docker Builds
&lt;/h2&gt;

&lt;p&gt;A common production approach is using separate stages:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build Stage
     ↓
Runtime Stage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Build Stage
&lt;/h3&gt;

&lt;p&gt;The build stage contains everything needed to create the application.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JDK
Gradle
source code
build tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After compilation finishes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;build/libs/application.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;is created.&lt;/p&gt;

&lt;p&gt;Then only that artifact moves forward.&lt;/p&gt;
&lt;h3&gt;
  
  
  Runtime Stage
&lt;/h3&gt;

&lt;p&gt;The runtime container only needs:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java Runtime
       +
Spring Boot JAR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It does not need:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source code

Gradle cache

compiler tools

temporary build files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This produces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;smaller images&lt;/li&gt;
&lt;li&gt;cleaner containers&lt;/li&gt;
&lt;li&gt;reduced attack surface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The container should contain what is required to run the application — nothing more.&lt;/p&gt;


&lt;h2&gt;
  
  
  Docker Layer Caching
&lt;/h2&gt;

&lt;p&gt;One of the biggest Docker performance improvements comes from understanding layers.&lt;/p&gt;

&lt;p&gt;A common mistake:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Copy everything
      ↓
Build everything
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every code change invalidates the cache.&lt;/p&gt;

&lt;p&gt;That means:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Change one controller
↓ 
Download dependencies again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A better approach:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Copy dependency files
↓
Download dependencies
↓
Copy source code
↓
Build application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now dependency layers remain cached until dependencies actually change.&lt;/p&gt;

&lt;p&gt;The result:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dependency change:
Full rebuild

Code change:
Only application rebuild
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Small ordering decisions can save minutes over hundreds of builds.&lt;/p&gt;


&lt;h2&gt;
  
  
  Local Development with Docker Compose
&lt;/h2&gt;

&lt;p&gt;Docker is not only for production.&lt;/p&gt;

&lt;p&gt;Most Spring Boot applications depend on services like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PostgreSQL
MySQL
MongoDB
Redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Instead of installing everything manually:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install database locally
Create users
Configure ports
Manage versions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Docker Compose defines the environment.&lt;/p&gt;

&lt;p&gt;A typical local setup contains:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application container
Database container
Network configuration
Environment variables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The goal:&lt;/p&gt;

&lt;p&gt;A new developer can run:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and get the same environment.&lt;/p&gt;


&lt;h2&gt;
  
  
  Environment-Based Configuration
&lt;/h2&gt;

&lt;p&gt;Local and production environments usually should not share the same configuration.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local:
database container

Production:
managed database service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Spring Boot commonly separates this using profiles:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application.yml
application-prod.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Local values might point to:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;localhost database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Production values usually come from:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;environment variables
deployment secrets
cloud configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Avoid:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;database passwords inside source code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Configuration should change without rebuilding the application.&lt;/p&gt;


&lt;h2&gt;
  
  
  Protecting the Docker Build Context
&lt;/h2&gt;

&lt;p&gt;A small but important detail is &lt;code&gt;.dockerignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Without it, Docker may receive unnecessary files:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.git
local build output
IDE files
environment files
logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These increase build size and can accidentally expose information.&lt;/p&gt;

&lt;p&gt;The Docker image should only receive what it actually needs.&lt;/p&gt;


&lt;h2&gt;
  
  
  Running Containers Securely
&lt;/h2&gt;

&lt;p&gt;Another common production improvement:&lt;/p&gt;

&lt;p&gt;Do not run containers as root.&lt;/p&gt;

&lt;p&gt;Default container behavior often runs as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A better setup creates:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;with limited permissions.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;If an application vulnerability happens:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root container user
        ↓
more permissions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Limiting permissions reduces risk.&lt;/p&gt;


&lt;h2&gt;
  
  
  Container Health Checks
&lt;/h2&gt;

&lt;p&gt;Production deployments need to know if your application&lt;br&gt;
is actually ready to receive traffic — not just running.&lt;/p&gt;

&lt;p&gt;Spring Boot Actuator exposes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/actuator/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Docker health checks and orchestration platforms can use this endpoint to determine when a container is healthy and ready to receive traffic.&lt;/p&gt;

&lt;p&gt;A container that starts but fails silently is harder to debug&lt;br&gt;
than one that correctly reports unhealthy.&lt;/p&gt;


&lt;h2&gt;
  
  
  JVM Memory Inside Containers
&lt;/h2&gt;

&lt;p&gt;Java applications also need container-aware memory settings.&lt;/p&gt;

&lt;p&gt;A server might have:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;16GB RAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;but your container may only have:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;512MB RAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The JVM should respect container limits.&lt;/p&gt;

&lt;p&gt;Production deployments often configure JVM memory based on container allocation instead of the host machine.&lt;/p&gt;

&lt;p&gt;This helps prevent unexpected memory failures.&lt;/p&gt;


&lt;h2&gt;
  
  
  Local vs Production Docker Setup
&lt;/h2&gt;

&lt;p&gt;A common structure:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project/
├── Dockerfile
├── docker-compose.yml
├── docker-compose.prod.yml
├── .env
└── .env.prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Local:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Docker Compose
Application
Local database container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Production:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Docker image
External database
Production secrets
Deployment automation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In larger environments, Docker Compose may be replaced by platforms like ECS or Kubernetes, but the container foundation remains the same.&lt;/p&gt;

&lt;p&gt;Same application.&lt;/p&gt;

&lt;p&gt;Different environments.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why This Setup Becomes Repetitive
&lt;/h2&gt;

&lt;p&gt;Dockerizing one Spring Boot application is useful experience.&lt;/p&gt;

&lt;p&gt;Dockerizing your fifth one feels different.&lt;/p&gt;

&lt;p&gt;Most projects need the same decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dockerfile structure&lt;/li&gt;
&lt;li&gt;build optimization&lt;/li&gt;
&lt;li&gt;Docker Compose setup&lt;/li&gt;
&lt;li&gt;environment handling&lt;/li&gt;
&lt;li&gt;production configuration&lt;/li&gt;
&lt;li&gt;security defaults&lt;/li&gt;
&lt;li&gt;deployment compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these pieces are impossible.&lt;/p&gt;

&lt;p&gt;But rebuilding them repeatedly slows down actually building the application.&lt;/p&gt;


&lt;h2&gt;
  
  
  Automating This Setup with SpringGen
&lt;/h2&gt;

&lt;p&gt;SpringGen generates Spring Boot projects with a Docker-ready foundation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;optimized multi-stage builds&lt;/li&gt;
&lt;li&gt;local Docker Compose configuration&lt;/li&gt;
&lt;li&gt;database container setup&lt;/li&gt;
&lt;li&gt;production environment structure&lt;/li&gt;
&lt;li&gt;deployment-ready configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The generated project is normal Spring Boot code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No runtime dependency

No vendor lock-in

Fully editable source code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Generate the foundation, then build your application on top.&lt;/p&gt;


&lt;h2&gt;
  
  
  Try SpringGen
&lt;/h2&gt;

&lt;p&gt;Free CLI(Generate a Starter Spring Boot project locally):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; springgen
springgen init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;GitHub:&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/springgen-dev" rel="noopener noreferrer"&gt;
        springgen-dev
      &lt;/a&gt; / &lt;a href="https://github.com/springgen-dev/springgen" rel="noopener noreferrer"&gt;
        springgen
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Generate &amp;amp; deploy a production-ready Spring Boot project with Docker, OAuth2, GitHub Actions CI/CD, and AWS EC2 — in minutes.
    &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;SpringGen — Generate Spring Boot Backends in Minutes 🚀  &lt;a href="https://www.npmjs.com/package/springgen" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/93b58755fc8f2957d2ebf3fa952b5848cc71f8a6b9c1bfca619269c6fa926b07/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f737072696e6767656e" alt="npm"&gt;&lt;/a&gt; &lt;a href="https://github.com/springgen-dev/springgen/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667" alt="License: MIT"&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Generate production-ready Spring Boot backends instantly — Authentication, Database, Docker, CI/CD, and AWS EC2 deployment included.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/springgen-dev/springgen#try-springgen-free-starter-cli" rel="noopener noreferrer"&gt;Try Free via CLI&lt;/a&gt;&lt;/strong&gt; •
🚀 &lt;strong&gt;&lt;a href="https://app.springgen.dev" rel="nofollow noopener noreferrer"&gt;Upgrade to Pro&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is SpringGen?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Every new backend service starts the same way — same structure, auth setup
database config, security hardening, Docker, and CI/CD pipeline. Most developers rebuild this
from scratch or copy-paste from a previous project every single time.&lt;/p&gt;
&lt;p&gt;SpringGen is built around that repeated workflow. Whether you are a freelancer spinning up a new client backend,
a founder building an MVP, or a developer starting another microservice — run the CLI or web UI, get a
consistent production-ready backend, and start writing business logic.&lt;/p&gt;
&lt;p&gt;No copy-pasting. No forgotten configs. No "how did I set this up last time."&lt;/p&gt;
&lt;p&gt;SpringGen uses a deterministic template/module engine to assemble
tested Spring Boot project structures, configurations, and production-ready…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/springgen-dev/springgen" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;App:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://app.springgen.dev" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;app.springgen.dev&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Docker is not only a deployment tool.&lt;/p&gt;

&lt;p&gt;It is a way to make application environments repeatable.&lt;/p&gt;

&lt;p&gt;A good Docker setup helps developers build, test, and deploy with confidence.&lt;/p&gt;

&lt;p&gt;Understanding Docker matters.&lt;/p&gt;

&lt;p&gt;Recreating the same setup forever does not.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>springboot</category>
      <category>java</category>
    </item>
    <item>
      <title>OAuth2 Login with JWT and Refresh Tokens in Spring Boot — The Setup You'll Rebuild Every Time</title>
      <dc:creator>Yadrs</dc:creator>
      <pubDate>Sat, 13 Jun 2026 14:12:13 +0000</pubDate>
      <link>https://dev.to/yadrs/oauth2-login-with-jwt-and-refresh-tokens-in-spring-boot-the-setup-youll-rebuild-every-time-40bn</link>
      <guid>https://dev.to/yadrs/oauth2-login-with-jwt-and-refresh-tokens-in-spring-boot-the-setup-youll-rebuild-every-time-40bn</guid>
      <description>&lt;p&gt;Most Spring Boot applications eventually need authentication.&lt;/p&gt;

&lt;p&gt;And many teams rebuild the same foundation every time.&lt;/p&gt;

&lt;p&gt;Add a login endpoint. Generate a token. Protect some APIs.&lt;br&gt;
Then production arrives — and the real requirements show up.&lt;/p&gt;

&lt;p&gt;A real backend often needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;external identity providers&lt;/li&gt;
&lt;li&gt;secure access tokens&lt;/li&gt;
&lt;li&gt;refresh token rotation&lt;/li&gt;
&lt;li&gt;user persistence&lt;/li&gt;
&lt;li&gt;token expiration handling&lt;/li&gt;
&lt;li&gt;protected APIs&lt;/li&gt;
&lt;li&gt;logout support&lt;/li&gt;
&lt;li&gt;environment-based secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where OAuth2 combined with JWT access tokens and refresh tokens becomes a common approach.&lt;/p&gt;

&lt;p&gt;This guide walks through the architecture, moving parts, and decisions behind building a production-style authentication flow with Spring Security.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem with Basic Token Authentication
&lt;/h2&gt;

&lt;p&gt;A simple JWT authentication flow usually looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User logs in
      ↓
Backend validates credentials
      ↓
Backend creates JWT
      ↓
Frontend stores JWT
      ↓
JWT is sent with API requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For small applications, this works.&lt;/p&gt;

&lt;p&gt;But production systems quickly need more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What happens when the JWT expires?

How does a user stay logged in?

How do you revoke access?

Where are user identities stored?

How do you support Google login?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why many applications move toward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OAuth2 Login
      +
JWT Access Tokens
      +
Refresh Tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Understanding the Authentication Flow
&lt;/h2&gt;

&lt;p&gt;A common production flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
Login with Google/GitHub
 ↓
OAuth2 Provider verifies identity
 ↓
Spring Security receives user info
 ↓
Backend creates application tokens
 ↓
Return:
   - short-lived access token
   - longer-lived refresh token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After login, the client does not call Google for every request.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
 ↓
Authorization: Bearer JWT
 ↓
Spring Boot API
 ↓
JWT validation
 ↓
Protected resource
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your backend remains responsible for securing its own APIs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Access Tokens vs Refresh Tokens
&lt;/h2&gt;

&lt;p&gt;A common mistake is treating one JWT as enough.&lt;/p&gt;

&lt;p&gt;A better pattern separates responsibilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access Token
&lt;/h3&gt;

&lt;p&gt;Purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authorize API requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;short expiration time&lt;/li&gt;
&lt;li&gt;sent with requests&lt;/li&gt;
&lt;li&gt;stateless validation&lt;/li&gt;
&lt;li&gt;contains limited claims&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expires in:
15 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Refresh Token
&lt;/h3&gt;

&lt;p&gt;Purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request a new access token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;longer lifetime&lt;/li&gt;
&lt;li&gt;stored securely&lt;/li&gt;
&lt;li&gt;persisted in database&lt;/li&gt;
&lt;li&gt;can be revoked&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expires in:
7-30 days
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access token expires
          ↓
Client sends refresh token
          ↓
Backend validates refresh token
          ↓
New access token issued
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user stays logged in without keeping permanent access tokens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Typical Spring Boot Structure
&lt;/h2&gt;

&lt;p&gt;A production authentication module usually introduces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;security/
├── JwtService           → creates and validates tokens
├── JwtAuthenticationFilter → intercepts requests before controllers
├── OAuth2SuccessHandler → handles post-login token generation
└── SecurityConfig       → wires the filter chain together

entity/
├── User
└── RefreshToken

repository/
├── UserRepository
└── RefreshTokenRepository

controller/
└── AuthController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each component has a responsibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  Spring Security Configuration
&lt;/h2&gt;

&lt;p&gt;Spring Security becomes the entry point.&lt;/p&gt;

&lt;p&gt;The configuration usually handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;public endpoints&lt;/li&gt;
&lt;li&gt;protected endpoints&lt;/li&gt;
&lt;li&gt;OAuth2 login&lt;/li&gt;
&lt;li&gt;JWT validation filters&lt;/li&gt;
&lt;li&gt;CORS configuration&lt;/li&gt;
&lt;li&gt;unauthorized responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incoming request
        ↓
Security filter chain
        ↓
JWT filter
        ↓
Authentication context
        ↓
Controller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controllers should not manually check tokens.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Security should happen before requests reach your business logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  OAuth2 Success Handling
&lt;/h2&gt;

&lt;p&gt;After a successful OAuth2 login:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Google authentication succeeds
          ↓
Spring Security receives user profile
          ↓
Find or create local user
          ↓
Generate application tokens
          ↓
Return tokens to client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application usually stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User:
- id
- email
- provider
- provider id

RefreshToken:
- token
- expiry
- associated user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets your application own authorization decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Token Refresh Flow
&lt;/h2&gt;

&lt;p&gt;Refreshing authentication usually looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /auth/refresh

Refresh token
       ↓
Validate token exists
       ↓
Check expiration
       ↓
Generate new JWT
       ↓
Return new access token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important considerations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;expire old tokens&lt;/li&gt;
&lt;li&gt;support logout&lt;/li&gt;
&lt;li&gt;remove compromised refresh tokens&lt;/li&gt;
&lt;li&gt;avoid unlimited token lifetime&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Production authentication should avoid hardcoded secrets.&lt;/p&gt;

&lt;p&gt;Common configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JWT_SECRET

JWT_EXPIRATION

GOOGLE_CLIENT_ID

GOOGLE_CLIENT_SECRET

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

&lt;/div&gt;



&lt;p&gt;These values should come from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;li&gt;deployment secrets&lt;/li&gt;
&lt;li&gt;cloud secret managers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Never commit production credentials.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Production Improvements
&lt;/h2&gt;

&lt;p&gt;After the basic flow works, teams often add:&lt;/p&gt;

&lt;h3&gt;
  
  
  Refresh Token Rotation
&lt;/h3&gt;

&lt;p&gt;Instead of reusing refresh tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use refresh token
        ↓
Delete old token
        ↓
Create new token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reduces risk if tokens leak.&lt;/p&gt;




&lt;h3&gt;
  
  
  Secure Cookies
&lt;/h3&gt;

&lt;p&gt;Some applications store tokens using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HttpOnly
Secure
SameSite
cookies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of browser storage.&lt;/p&gt;

&lt;p&gt;The best choice depends on frontend architecture.&lt;/p&gt;




&lt;h3&gt;
  
  
  Rate Limiting
&lt;/h3&gt;

&lt;p&gt;Authentication endpoints should be protected:&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/auth/login

/auth/refresh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to reduce abuse.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Goes Wrong in Production
&lt;/h2&gt;

&lt;p&gt;A few things that catch teams off guard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Refresh tokens stored in localStorage are vulnerable to XSS.&lt;br&gt;
HttpOnly cookies are safer but require careful CORS configuration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Token expiration mismatches between frontend and backend&lt;br&gt;
can cause confusing 401 errors that are hard to debug.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refresh token rotation without proper cleanup can create&lt;br&gt;
orphaned tokens that accumulate in your database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Logout that only clears frontend storage is not a complete logout.&lt;br&gt;
The refresh token may still remain valid until expiration.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why This Setup Becomes Repetitive
&lt;/h2&gt;

&lt;p&gt;OAuth2 authentication is not just adding a dependency.&lt;/p&gt;

&lt;p&gt;A complete setup usually includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring Security configuration&lt;/li&gt;
&lt;li&gt;OAuth2 handlers&lt;/li&gt;
&lt;li&gt;JWT utilities&lt;/li&gt;
&lt;li&gt;refresh token persistence&lt;/li&gt;
&lt;li&gt;database entities&lt;/li&gt;
&lt;li&gt;repositories&lt;/li&gt;
&lt;li&gt;exception handling&lt;/li&gt;
&lt;li&gt;environment configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these pieces are impossible.&lt;/p&gt;

&lt;p&gt;But most projects rebuild a very similar foundation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automating This Setup with SpringGen
&lt;/h2&gt;

&lt;p&gt;After you understand the architecture, rebuilding these files for every new project becomes repetitive.&lt;/p&gt;

&lt;p&gt;SpringGen helps generate Spring Boot backend foundations with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth2 authentication setup&lt;/li&gt;
&lt;li&gt;JWT access tokens&lt;/li&gt;
&lt;li&gt;refresh token flow&lt;/li&gt;
&lt;li&gt;database configuration&lt;/li&gt;
&lt;li&gt;security structure&lt;/li&gt;
&lt;li&gt;Docker setup&lt;/li&gt;
&lt;li&gt;deployment configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generated projects are normal Spring Boot applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No runtime dependency
No vendor lock-in
Fully editable source code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;p&gt;Spend less time rebuilding authentication infrastructure and more time building the application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try SpringGen
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/springgen-dev/springgen" rel="noopener noreferrer"&gt;https://github.com/springgen-dev/springgen&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;App: &lt;a href="https://app.springgen.dev" rel="noopener noreferrer"&gt;https://app.springgen.dev&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Authentication is one of the first systems every backend needs.&lt;/p&gt;

&lt;p&gt;A good authentication setup is not only about logging users in.&lt;/p&gt;

&lt;p&gt;It is about creating a foundation that is secure, maintainable, and ready to evolve.&lt;/p&gt;

&lt;p&gt;Understanding the pieces matters.&lt;/p&gt;

&lt;p&gt;Repeating the same setup forever does not.&lt;/p&gt;

</description>
      <category>java</category>
      <category>spring</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>GDPR Compliance in a Spring Boot App — The Implementation Nobody Talks About</title>
      <dc:creator>Yadrs</dc:creator>
      <pubDate>Thu, 11 Jun 2026 13:55:24 +0000</pubDate>
      <link>https://dev.to/yadrs/gdpr-compliance-in-a-spring-boot-app-the-implementation-nobody-talks-about-1j40</link>
      <guid>https://dev.to/yadrs/gdpr-compliance-in-a-spring-boot-app-the-implementation-nobody-talks-about-1j40</guid>
      <description>&lt;p&gt;Most GDPR articles stop at cookie banners and privacy policies.&lt;/p&gt;

&lt;p&gt;That is the visible layer. The part users see.&lt;/p&gt;

&lt;p&gt;The harder part is what happens inside your backend — how you store&lt;br&gt;
personal data, how you handle deletion requests, how you prove&lt;br&gt;
compliance when someone asks.&lt;/p&gt;

&lt;p&gt;I spent years implementing data privacy controls at an enterprise level.&lt;/p&gt;

&lt;p&gt;This is the implementation side that most tutorials skip.&lt;/p&gt;


&lt;h2&gt;
  
  
  What GDPR Actually Requires From Your Backend
&lt;/h2&gt;

&lt;p&gt;GDPR gives users specific rights over their personal data.&lt;/p&gt;

&lt;p&gt;The ones that directly affect your Spring Boot backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Right to Access      → User can request all data you hold about them
Right to Erasure     → User can request deletion of their data
Right to Portability → User can request their data in a portable format
Right of Rectification → User can request correction of inaccurate data
Breach Notification  → You must notify users within 72 hours of a breach
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these is not just a policy decision. Each one requires actual code.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Know What Personal Data You Are Storing
&lt;/h2&gt;

&lt;p&gt;Before writing any compliance code, you need a data inventory.&lt;/p&gt;

&lt;p&gt;Personal data under GDPR includes anything that can identify a person — directly or indirectly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Direct identifiers:
- name
- email address
- phone number
- IP address
- user ID

Indirect identifiers:
- device fingerprint
- location data
- behavioral data (clicks, sessions)
- combination of fields that together identify someone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Map every table in your database. For each column, ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is this personal data?
Why are we storing it?
How long do we need it?
Who can access it?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is your Record of Processing Activities (RoPA) — a GDPR requirement for most organizations.&lt;/p&gt;

&lt;p&gt;If you cannot answer why you are storing a piece of data, you probably should not be storing it.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Classify Your Data at the Model Level
&lt;/h2&gt;

&lt;p&gt;One practical approach is annotating your JPA entities to make PII visible in your codebase.&lt;/p&gt;

&lt;p&gt;Create a simple annotation:&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;@Retention&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RetentionPolicy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RUNTIME&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Target&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ElementType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FIELD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nd"&gt;@interface&lt;/span&gt; &lt;span class="nc"&gt;PersonalData&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;category&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="s"&gt;"general"&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;purpose&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;canBeDeleted&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kc"&gt;true&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;Use it on your entities:&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;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@PersonalData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;purpose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"authentication"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"contact"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&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="nd"&gt;@PersonalData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;purpose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"personalization"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"contact"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;fullName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@PersonalData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;purpose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"analytics"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"technical"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;canBeDeleted&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;ipAddress&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// not personal data&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does two things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Makes PII visible to every developer reading the code
Gives you a foundation for automated compliance tooling later
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will not enforce anything on its own. But it documents intent in the place that matters — the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Implement the Right to Erasure
&lt;/h2&gt;

&lt;p&gt;The right to erasure (right to be forgotten) is the most technically complex GDPR requirement for a backend developer.&lt;/p&gt;

&lt;p&gt;When a user requests deletion, you have to decide between two approaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hard delete   → Remove the record entirely from the database
Soft delete   → Anonymize the record, retain the row for audit/integrity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For most applications, hard deletion across all tables is impractical. Foreign key constraints, audit logs, financial records, and legal retention requirements all complicate it.&lt;/p&gt;

&lt;p&gt;A more realistic approach is &lt;strong&gt;pseudonymization&lt;/strong&gt; — replacing personal data with anonymized values while retaining non-personal data you have a legitimate reason to keep.&lt;/p&gt;

&lt;p&gt;Example erasure service:&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;@Service&lt;/span&gt;
&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataErasureService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;AuditLogRepository&lt;/span&gt; &lt;span class="n"&gt;auditLogRepository&lt;/span&gt;&lt;span class="o"&gt;;&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;eraseUser&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;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;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&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;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Anonymize personal fields&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;setEmail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"deleted-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"@anonymized.invalid"&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;setFullName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Deleted User"&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;setIpAddress&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="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPhoneNumber&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="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDeletedAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&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;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ERASED&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;userRepository&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;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Record the erasure for your audit log&lt;/span&gt;
        &lt;span class="n"&gt;auditLogRepository&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="nc"&gt;AuditLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"USER_ERASED"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subjectId&lt;/span&gt;&lt;span class="o"&gt;(&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;span class="na"&gt;performedAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&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;Important considerations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keep a record that erasure happened — not what was erased
Check for financial or legal retention obligations before deleting
Cascade the erasure to related services and tables
Do not forget logs — application logs often contain email addresses
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Implement the Right to Access (Data Subject Access Request)
&lt;/h2&gt;

&lt;p&gt;When a user requests all data you hold about them, you need to be able to produce it.&lt;/p&gt;

&lt;p&gt;A data subject access request (DSAR) endpoint:&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;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/privacy"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PrivacyController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;DataExportService&lt;/span&gt; &lt;span class="n"&gt;dataExportService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/export"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserDataExport&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;exportMyData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nd"&gt;@AuthenticationPrincipal&lt;/span&gt; &lt;span class="nc"&gt;UserDetails&lt;/span&gt; &lt;span class="n"&gt;userDetails&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;UserDataExport&lt;/span&gt; &lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataExportService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exportForUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;userDetails&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUsername&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="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;export&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;The export service aggregates data across your tables:&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;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataExportService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;UserDataExport&lt;/span&gt; &lt;span class="nf"&gt;exportForUser&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;return&lt;/span&gt; &lt;span class="nc"&gt;UserDataExport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByEmail&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="na"&gt;transactions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transactionRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByUserEmail&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="na"&gt;preferences&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;preferenceRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByUserEmail&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="na"&gt;auditEvents&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auditRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByUserEmail&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="na"&gt;exportedAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&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;Return this as JSON or generate a PDF export depending on your use case.&lt;/p&gt;

&lt;p&gt;Key things to include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;All profile data
All activity data you hold
Preferences and settings
How long you retain each type of data
Third parties you share data with
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Consent Management
&lt;/h2&gt;

&lt;p&gt;If you rely on consent as your legal basis for processing, you need to record it — not just collect it.&lt;/p&gt;

&lt;p&gt;A consent record is not a checkbox in your UI. It is a timestamped, versioned record in your database.&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;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConsentRecord&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;consentType&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// e.g. MARKETING, ANALYTICS&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;consentVersion&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// version of your privacy policy&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;granted&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;recordedAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;ipAddress&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userAgent&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;Every time consent is given or withdrawn, you write a new record. You never update the old one.&lt;/p&gt;

&lt;p&gt;This gives you an audit trail you can produce if your processing is ever challenged.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Data Retention — Automate the Cleanup
&lt;/h2&gt;

&lt;p&gt;Keeping personal data longer than necessary can violate GDPR data minimization and storage limitation principles.&lt;/p&gt;

&lt;p&gt;Define retention periods for each data category and automate deletion using Spring Batch or a scheduled job:&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;@Component&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataRetentionJob&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Delete inactive user data after 2 years&lt;/span&gt;
    &lt;span class="nd"&gt;@Scheduled&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cron&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0 0 2 * * SUN"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Transactional&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;purgeExpiredUserData&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;cutoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;minus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;730&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ChronoUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DAYS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;expiredUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findInactiveUsersBefore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cutoff&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;expiredUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forEach&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;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dataErasureService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;eraseUser&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="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Data retention job completed. Processed {} users."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expiredUsers&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;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;Document your retention periods somewhere accessible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User profiles         → 2 years after last login
Transaction records   → 7 years (legal requirement)
Application logs      → 90 days
Consent records       → Duration of relationship + 1 year
Anonymized analytics  → Indefinite (no longer personal data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Audit Logging for Compliance
&lt;/h2&gt;

&lt;p&gt;You need to be able to answer: who accessed what personal data, and when.&lt;/p&gt;

&lt;p&gt;A simple audit log entity:&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;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuditLog&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// DATA_ACCESSED, DATA_ERASED, CONSENT_GIVEN&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;subjectId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// the user whose data was affected&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;actorId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// who performed the action&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;resourceType&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// USER_PROFILE, TRANSACTION, etc.&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;performedAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;ipAddress&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;Write audit events for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Any access to personal data via API
All erasure requests and outcomes
All consent changes
Any data exports
Any admin access to user records
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This audit trail is what you produce during a regulatory investigation or a user complaint.&lt;/p&gt;

&lt;p&gt;Security configuration is also part of privacy engineering.&lt;/p&gt;

&lt;p&gt;Access controls should ensure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;users only access their own data&lt;/li&gt;
&lt;li&gt;admin actions are restricted&lt;/li&gt;
&lt;li&gt;sensitive endpoints require authentication&lt;/li&gt;
&lt;li&gt;authorization checks are tested&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Authentication is only the first step.&lt;br&gt;
Correct authorization is what protects personal data.&lt;/p&gt;


&lt;h2&gt;
  
  
  8. What About Application Logs?
&lt;/h2&gt;

&lt;p&gt;This is the most commonly overlooked GDPR issue in Spring Boot applications.&lt;/p&gt;

&lt;p&gt;Application logs almost always contain personal data — email addresses in authentication logs, user IDs in request logs, IP addresses in access logs.&lt;/p&gt;

&lt;p&gt;Two practical steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Mask personal data in logs:&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;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PersonalDataMaskingConverter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ClassicConverter&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Pattern&lt;/span&gt; &lt;span class="no"&gt;EMAIL_PATTERN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ILoggingEvent&lt;/span&gt; &lt;span class="n"&gt;event&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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFormattedMessage&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;EMAIL_PATTERN&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;matcher&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="na"&gt;replaceAll&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;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2 — Set a log retention policy:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Logs should not be retained indefinitely. 30 to 90 days is a common default.&lt;br&gt;
Configure this in your log aggregation system — CloudWatch, Splunk, or whatever you use.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Part Nobody Warns You About
&lt;/h2&gt;

&lt;p&gt;Building the compliance controls is the straightforward part.&lt;/p&gt;

&lt;p&gt;The harder part is maintaining them as your application grows.&lt;/p&gt;

&lt;p&gt;New developers join and add new fields without thinking about PII.&lt;br&gt;
A new service gets added that logs user data in a new format.&lt;br&gt;
A third-party integration starts receiving personal data without a data processing agreement.&lt;/p&gt;

&lt;p&gt;The controls that matter most are not technical — they are process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Code review checklist that asks "does this field contain personal data?"&lt;/li&gt;
&lt;li&gt;  Data processing agreements with every third-party service you send data to&lt;/li&gt;
&lt;li&gt;  A documented process for handling erasure requests within 30 days&lt;/li&gt;
&lt;li&gt;  A documented process for breach notification within 72 hours&lt;/li&gt;
&lt;li&gt;  Regular review of what data you are actually storing vs what you documented&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The annotation approach from step 2 helps with this — it makes PII visible in code reviews.&lt;/p&gt;

&lt;p&gt;But there is no substitute for making compliance part of your engineering culture, not just your legal documentation.&lt;/p&gt;


&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;GDPR compliance in a Spring Boot backend comes down to six concrete things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Know what personal data you store and why
2. Implement erasure — anonymize or delete on request
3. Implement data export — produce user data on request
4. Record consent with timestamps and versions
5. Automate data retention — delete what you no longer need
6. Audit log all access and changes to personal data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of these are impossible to build.&lt;/p&gt;

&lt;p&gt;The challenge is building all of them, keeping them working as your application grows,&lt;br&gt;
and making sure new features respect the same rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  Starting With a Production Foundation
&lt;/h2&gt;

&lt;p&gt;If you are starting a new Spring Boot backend, SpringGen generates the security scaffolding, structured logging configuration, and production-ready defaults that give you a solid foundation before you write your first line of business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The compliance layer on top of that foundation is still your responsibility.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But starting with clean structure, proper security defaults, and production configuration in place removes one layer of things to get right from scratch.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/springgen-dev/springgen" rel="noopener noreferrer"&gt;https://github.com/springgen-dev/springgen&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;App: &lt;a href="https://app.springgen.dev" rel="noopener noreferrer"&gt;https://app.springgen.dev&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article reflects practical implementation patterns for GDPR compliance in Spring Boot applications.&lt;br&gt;
It is not legal advice. Consult a qualified lawyer or DPO for your specific compliance obligations.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>spring</category>
      <category>gdpr</category>
      <category>security</category>
    </item>
    <item>
      <title>Deploying Spring Boot to AWS EC2 with Docker and GitHub Actions — The Repeatable Way</title>
      <dc:creator>Yadrs</dc:creator>
      <pubDate>Tue, 09 Jun 2026 15:25:09 +0000</pubDate>
      <link>https://dev.to/yadrs/deploying-spring-boot-to-aws-ec2-with-docker-and-github-actions-the-repeatable-way-55bi</link>
      <guid>https://dev.to/yadrs/deploying-spring-boot-to-aws-ec2-with-docker-and-github-actions-the-repeatable-way-55bi</guid>
      <description>&lt;p&gt;Deploying a Spring Boot application to AWS EC2 is straightforward once you've done it.&lt;/p&gt;

&lt;p&gt;Getting there the first time — and repeating the setup on your next few projects — is where the real cost appears.&lt;/p&gt;

&lt;p&gt;A production deployment is usually not just about running a JAR file.&lt;/p&gt;

&lt;p&gt;For a real-world backend, deployment often involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;separating local and production configuration&lt;/li&gt;
&lt;li&gt;building the application consistently&lt;/li&gt;
&lt;li&gt;packaging it with Docker&lt;/li&gt;
&lt;li&gt;managing environment variables and secrets&lt;/li&gt;
&lt;li&gt;connecting to an EC2 instance&lt;/li&gt;
&lt;li&gt;restarting the app safely&lt;/li&gt;
&lt;li&gt;automating the process after every push&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide walks through the deployment workflow at a high level, the decisions involved, and why this setup becomes repetitive across projects.&lt;/p&gt;

&lt;p&gt;The goal is not to hide the details. You should understand your deployment pipeline.&lt;/p&gt;

&lt;p&gt;The real pain starts when you have to rebuild the same foundation again and again.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Deployment Flow
&lt;/h2&gt;

&lt;p&gt;A typical Spring Boot EC2 deployment with GitHub Actions looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Push to main branch
        ↓
GitHub Actions runs cd.yml
        ↓
GitHub Actions connects to EC2 using SSH
        ↓
EC2 pulls the latest code
        ↓
Production environment variables are prepared
        ↓
Deployment script runs
        ↓
Docker rebuilds and restarts the application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;At a high level, the moving parts are:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot app
Docker
GitHub Actions
AWS EC2
Environment variables
Deployment script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Each piece is simple enough on its own.&lt;/p&gt;

&lt;p&gt;The complexity comes from wiring them together correctly and repeating that setup for every new backend.&lt;/p&gt;


&lt;h2&gt;
  
  
  1. Separate Local and Production Configuration
&lt;/h2&gt;

&lt;p&gt;A production-ready Spring Boot project usually needs different configuration for local development and production.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/main/resources/
├── application.yml
└── application-prod.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Local configuration may use local Docker containers.&lt;/p&gt;

&lt;p&gt;Production configuration should rely on environment variables.&lt;/p&gt;

&lt;p&gt;Example production config pattern:&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;datasource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${SPRING_DATASOURCE_URL}&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${SPRING_DATASOURCE_USERNAME}&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${SPRING_DATASOURCE_PASSWORD}&lt;/span&gt;

&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${SERVER_PORT:8080}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The important principle:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do not hardcode production secrets in source code.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Use environment variables, GitHub Secrets, AWS Secrets Manager, or another secret management approach.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. Add Docker Packaging
&lt;/h2&gt;

&lt;p&gt;Docker gives the application a consistent runtime environment.&lt;/p&gt;

&lt;p&gt;A Spring Boot deployment usually needs:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dockerfile
docker-compose.yml
docker-compose.prod.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Dockerfile packages the Spring Boot application.&lt;/p&gt;

&lt;p&gt;The Compose files help define how the app runs locally or in production.&lt;/p&gt;

&lt;p&gt;A simplified Docker flow looks like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build JAR
        ↓
Build Docker image
        ↓
Run container
        ↓
Expose app on port 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The exact Dockerfile can vary depending on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java version&lt;/li&gt;
&lt;li&gt;Gradle vs Maven&lt;/li&gt;
&lt;li&gt;multi-stage build preference&lt;/li&gt;
&lt;li&gt;whether you want smaller runtime images&lt;/li&gt;
&lt;li&gt;whether the app needs extra OS packages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key is consistency.&lt;/p&gt;

&lt;p&gt;Once Docker is in place, the app runs the same way every time.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Prepare the EC2 Instance
&lt;/h2&gt;

&lt;p&gt;Before GitHub Actions can deploy anything, the EC2 &lt;br&gt;
instance needs to be ready to receive it.&lt;/p&gt;

&lt;p&gt;An EC2 deployment needs a server with the required runtime tools installed.&lt;/p&gt;

&lt;p&gt;At minimum, the EC2 instance usually needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;access to the repository&lt;/li&gt;
&lt;li&gt;an SSH user&lt;/li&gt;
&lt;li&gt;firewall/security group allowing app traffic&lt;/li&gt;
&lt;li&gt;environment configuration for production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The deployment process should avoid manually copying files whenever possible.&lt;/p&gt;

&lt;p&gt;Instead, the EC2 instance can pull the latest code from the repository and run a deployment script.&lt;/p&gt;

&lt;p&gt;That keeps the deployment repeatable.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. Use GitHub Actions as the Deployment Trigger
&lt;/h2&gt;

&lt;p&gt;GitHub Actions becomes the automation layer.&lt;/p&gt;

&lt;p&gt;The workflow usually does this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Checkout repository
        ↓
Connect to EC2 over SSH
        ↓
Pull latest code on EC2
        ↓
Create or update production environment variables
        ↓
Run deployment script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A simplified workflow shape looks like this:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Spring Boot&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to EC2&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appleboy/ssh-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.EC2_HOST }}&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.EC2_USER }}&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.EC2_SSH_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;cd my-app&lt;/span&gt;
            &lt;span class="s"&gt;git pull origin main&lt;/span&gt;
            &lt;span class="s"&gt;./scripts/deploy-ec2.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is intentionally simplified.&lt;/p&gt;

&lt;p&gt;A real workflow may also handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;private repositories&lt;/li&gt;
&lt;li&gt;GitHub personal access tokens&lt;/li&gt;
&lt;li&gt;production &lt;code&gt;.env&lt;/code&gt; creation&lt;/li&gt;
&lt;li&gt;Docker installation checks&lt;/li&gt;
&lt;li&gt;branch-specific deployments&lt;/li&gt;
&lt;li&gt;build validation&lt;/li&gt;
&lt;li&gt;rollback strategy&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  5. Use a Deployment Script on EC2
&lt;/h2&gt;

&lt;p&gt;Instead of putting all deployment logic directly inside GitHub Actions, it is cleaner to keep the actual app deployment logic in a script inside the project.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scripts/deploy-ec2.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A deployment script typically handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stopping the existing container&lt;/li&gt;
&lt;li&gt;removing old containers/images when needed&lt;/li&gt;
&lt;li&gt;rebuilding the Docker image&lt;/li&gt;
&lt;li&gt;starting the updated container&lt;/li&gt;
&lt;li&gt;loading production environment variables&lt;/li&gt;
&lt;li&gt;keeping deployment commands consistent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simplified script flow:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read environment config
        ↓
Stop existing container
        ↓
Build latest Docker image
        ↓
Start new container
        ↓
Verify app is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is useful because the script can be used in two ways:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Automated:
GitHub Actions runs it during CI/CD

Manual:
SSH into EC2 and run it directly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That gives flexibility without duplicating deployment logic.&lt;/p&gt;


&lt;h2&gt;
  
  
  6. Manage Production Environment Variables
&lt;/h2&gt;

&lt;p&gt;Production deployments usually need a &lt;code&gt;.env.prod&lt;/code&gt; or equivalent environment configuration.&lt;/p&gt;

&lt;p&gt;Typical values include:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_NAME
SERVER_PORT
SPRING_PROFILES_ACTIVE

SPRING_DATASOURCE_URL
SPRING_DATASOURCE_USERNAME
SPRING_DATASOURCE_PASSWORD

JWT_SECRET
OAUTH_CLIENT_ID
OAUTH_CLIENT_SECRET

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

&lt;/div&gt;


&lt;p&gt;There are two common approaches.&lt;/p&gt;
&lt;h3&gt;
  
  
  Option A — GitHub Actions creates the production env file
&lt;/h3&gt;

&lt;p&gt;GitHub Secrets store sensitive values.&lt;/p&gt;

&lt;p&gt;During deployment, GitHub Actions writes those secrets into &lt;code&gt;.env.prod&lt;/code&gt; on the EC2 instance.&lt;/p&gt;

&lt;p&gt;This keeps secrets out of source code.&lt;/p&gt;
&lt;h3&gt;
  
  
  Option B — Create &lt;code&gt;.env.prod&lt;/code&gt; manually once on EC2
&lt;/h3&gt;

&lt;p&gt;For a simpler setup, you can SSH into EC2 and create the production env file once.&lt;/p&gt;

&lt;p&gt;Then future deployments reuse it.&lt;/p&gt;

&lt;p&gt;This is useful for smaller projects or manual deployments.&lt;/p&gt;


&lt;h2&gt;
  
  
  7. What This Looks Like End-to-End
&lt;/h2&gt;

&lt;p&gt;The complete deployment system looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer
   ↓
Pushes code to main
   ↓
GitHub Actions starts
   ↓
SSH connection to EC2
   ↓
EC2 pulls latest code
   ↓
Production env config is prepared
   ↓
scripts/deploy-ec2.sh runs
   ↓
Docker rebuilds app
   ↓
Old container is replaced
   ↓
Spring Boot app runs on EC2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is a practical setup for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;side projects&lt;/li&gt;
&lt;li&gt;MVPs&lt;/li&gt;
&lt;li&gt;internal tools&lt;/li&gt;
&lt;li&gt;freelance client backends&lt;/li&gt;
&lt;li&gt;small SaaS applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not the only way to deploy Spring Boot.&lt;/p&gt;

&lt;p&gt;But it is a useful middle ground between:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Manual SSH deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Full Kubernetes / enterprise platform setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Becomes Repetitive
&lt;/h2&gt;

&lt;p&gt;The first time you set this up, it is a learning exercise.&lt;/p&gt;

&lt;p&gt;The third or fourth time, it becomes repetitive.&lt;/p&gt;

&lt;p&gt;Every new Spring Boot backend needs similar decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;project structure&lt;/li&gt;
&lt;li&gt;Docker setup&lt;/li&gt;
&lt;li&gt;production config&lt;/li&gt;
&lt;li&gt;deployment workflow&lt;/li&gt;
&lt;li&gt;CI/CD files&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;li&gt;security defaults&lt;/li&gt;
&lt;li&gt;logging configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these pieces are impossible.&lt;/p&gt;

&lt;p&gt;But wiring them together correctly takes time.&lt;/p&gt;

&lt;p&gt;And if you are building multiple projects, client backends, SaaS ideas, or microservices, that setup cost repeats.&lt;/p&gt;


&lt;h2&gt;
  
  
  Automating Repetitive Backend Setup
&lt;/h2&gt;

&lt;p&gt;This is the kind of repetitive backend setup that SpringGen is designed to automate.&lt;/p&gt;

&lt;p&gt;SpringGen creates a ready-to-customize Spring Boot foundation with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;layered backend structure&lt;/li&gt;
&lt;li&gt;database configuration&lt;/li&gt;
&lt;li&gt;authentication scaffolding&lt;/li&gt;
&lt;li&gt;Docker setup&lt;/li&gt;
&lt;li&gt;GitHub Actions workflows&lt;/li&gt;
&lt;li&gt;AWS EC2 deployment automation&lt;/li&gt;
&lt;li&gt;security hardening&lt;/li&gt;
&lt;li&gt;logging configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to replace understanding Spring Boot or DevOps.&lt;/p&gt;

&lt;p&gt;The goal is to remove the repetitive setup after you already know what kind of backend foundation you want.&lt;/p&gt;

&lt;p&gt;Generated projects are standard Spring Boot code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No generator runtime dependency
No framework lock-in
No frontend opinions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You generate the project, then own and modify the code like any normal Spring Boot application.&lt;/p&gt;


&lt;h2&gt;
  
  
  Try SpringGen
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/springgen-dev/springgen" rel="noopener noreferrer"&gt;https://github.com/springgen-dev/springgen&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;App: &lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://app.springgen.dev" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;app.springgen.dev&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Demo video:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/FoQvdnamsqs"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;A good deployment pipeline is not just about pushing code to a server.&lt;/p&gt;

&lt;p&gt;It is about making deployment predictable, repeatable, and easy to maintain.&lt;/p&gt;

&lt;p&gt;You can absolutely build every piece of this manually — and understanding how it works is important.&lt;/p&gt;

&lt;p&gt;But after you have configured the same Docker setup, CI/CD workflow, environment files, and deployment scripts across multiple projects, the setup itself becomes the repetitive part.&lt;/p&gt;

&lt;p&gt;That repetition is what SpringGen is designed to remove.&lt;/p&gt;

&lt;p&gt;Spend less time rebuilding the foundation, and more time building the application.&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>java</category>
      <category>aws</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
