{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreidmohxelpwsmybvrkms3i6sllxjrwsgozvu6gkyhxw6wxewpvq3fi",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpvjsgnq2er2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreibdddtffovxonbdyn6dyqmb4yv6gntpagystoamiql5rd5uc5odpm"
},
"mimeType": "image/webp",
"size": 533188
},
"path": "/sujankim/i-built-a-production-ready-spring-boot-410-saas-boilerplate-here-is-what-i-learned-2164",
"publishedAt": "2026-07-05T11:18:58.000Z",
"site": "https://dev.to",
"tags": [
"springboot",
"java",
"webdev",
"opensource",
"https://sujankim.github.io/springlaunch",
"@MockBean",
"@MockitoBean",
"@Async",
"@Service",
"@Entity",
"@NoArgsConstructor",
"@RequestMapping"
],
"textContent": "> Why I Built This\n\nEvery Spring Boot project I started, I spent the first **2–3 weeks** building the exact same things:\n\n * JWT authentication\n * Email verification\n * Forgot password flow\n * Google OAuth2 integration\n * Docker setup\n * CI/CD pipeline\n\n\n\nThat is weeks of work before writing a single line of my actual product.\n\nSo I packaged all of it into **SpringLaunch API** — a production-ready Spring Boot **4.1.0** boilerplate.\n\nHere are the biggest lessons I learned while building it.\n\n## Spring Boot 4 Changed A Lot\n\nSpring Boot **4.1.0** (released June 2026) is a major release.\n\nSeveral APIs changed compared to Boot 3.\n\n## Test packages moved\n\n\n // Spring Boot 3\n import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;\n\n // Spring Boot 4\n import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;\n\n\n## `@MockBean` became `@MockitoBean`\n\n\n // Spring Boot 3\n @MockBean\n private JwtService jwtService;\n\n // Spring Boot 4\n @MockitoBean\n private JwtService jwtService;\n\n\n## `DaoAuthenticationProvider` is now auto-configured\n\nIf your application exposes both:\n\n * `UserDetailsService`\n * `PasswordEncoder`\n\n\n\nSpring Security automatically creates the `DaoAuthenticationProvider`.\n\nNo manual bean configuration is required anymore.\n\n## JWT Strategy — Two Tokens, Two Places\n\nI use a hybrid authentication strategy.\n\nToken | Lifetime | Storage\n---|---|---\nAccess Token | 15 minutes | JSON response body\nRefresh Token | 7 days | HTTP-only Cookie\n\nAccess token:\n\n\n\n public record AuthResponse(\n String accessToken,\n UserResponse user\n ) {}\n\n\nRefresh token:\n\n\n\n ResponseCookie cookie = ResponseCookie.from(\"refreshToken\", token)\n .httpOnly(true)\n .secure(true)\n .sameSite(\"Lax\")\n .path(\"/\")\n .maxAge(maxAgeSeconds)\n .build();\n\n\n### Why HTTP-only cookies?\n\nJavaScript cannot read them.\n\nEven if an XSS attack executes on the page, it cannot steal the refresh token.\n\n## The `@Async` Self-Invocation Trap\n\nThis bug cost me over an hour.\n\nCalling an `@Async` method from the same class bypasses Spring's proxy, so the method executes **synchronously**.\n\n❌ Wrong\n\n\n\n @Service\n public class AuthServiceImpl {\n\n @Async\n private void sendEmail() { }\n\n public void register() {\n sendEmail();\n }\n }\n\n\n✅ Correct\n\n\n\n @Service\n public class EmailService {\n\n @Async\n public void sendEmail() { }\n }\n\n @Service\n public class AuthServiceImpl {\n\n private final EmailService emailService;\n\n public void register() {\n emailService.sendEmail();\n }\n }\n\n\nBecause the call goes through Spring's proxy, it becomes truly asynchronous.\n\n## Argon2 — Use Password4j\n\nSpring Security's `Argon2PasswordEncoder` is soft-deprecated.\n\nInstead, Boot 4 recommends Password4j integration.\n\n❌ Old\n\n\n\n new Argon2PasswordEncoder(\n 16,\n 32,\n 1,\n 65536,\n 3\n );\n\n\n✅ Recommended\n\n\n\n import org.springframework.security.crypto.password4j.Argon2Password4jPasswordEncoder;\n\n new Argon2Password4jPasswordEncoder();\n\n\nCleaner code with recommended defaults.\n\n## Factory Methods On JPA Entities\n\nJPA entities cannot be records because they require:\n\n * mutable fields\n * a no-argument constructor\n\n\n\nInstead, I use factory methods.\n\n\n\n @Entity\n @NoArgsConstructor(access = AccessLevel.PROTECTED)\n public class User extends BaseEntity implements UserDetails {\n\n public static User ofLocal(\n String name,\n String username,\n String email,\n String encodedPassword\n ) {\n\n User user = new User();\n\n user.name = name.trim();\n user.username = username.toLowerCase();\n user.email = email.toLowerCase();\n user.password = encodedPassword;\n\n user.role = UserRole.USER;\n user.provider = AuthProvider.LOCAL;\n user.emailVerified = false;\n\n return user;\n }\n\n public static User ofGoogle(\n String name,\n String username,\n String email,\n String providerId\n ) {\n\n User user = new User();\n\n // ...\n\n user.emailVerified = true;\n\n return user;\n }\n }\n\n\nNo one can accidentally create an invalid user.\n\n## API Versioning From Day One\n\nEvery endpoint is versioned.\n\n\n\n public final class ApiVersion {\n\n public static final String V1 = \"/v1\";\n\n private ApiVersion() {}\n }\n\n\nControllers simply use:\n\n\n\n @RequestMapping(ApiVersion.V1 + \"/auth\")\n public class AuthController {\n }\n\n\nWhen breaking changes arrive:\n\n * Add `/v2`\n * Keep `/v1`\n * Existing clients never break\n\n\n\n## What Is Included\n\nSpringLaunch API currently includes:\n\n * Spring Boot 4.1.0\n * Java 21\n * JWT Authentication\n * Google OAuth2 Login\n * Email Verification\n * Password Reset\n * 17 REST Endpoints\n * 42 Automated Tests\n * Docker Compose\n * GitHub Actions CI\n * Render Deployment Configuration\n * 7 Documentation Guides\n\n\n\n## Final Thoughts\n\nBuilding this taught me far more than just authentication.\n\nI learned how Spring Boot 4 changed testing, security, password encoding, asynchronous execution, and application architecture.\n\nMost importantly, I now have a production-ready starting point that saves **weeks** every time I build a new SaaS.\n\nIf you're interested, you can check out **SpringLaunch API** here:\n\n**Landing Page**\n\nhttps://sujankim.github.io/springlaunch\n\nIt's also available on Gumroad.\n\nI'd love to hear your thoughts or answer any questions about the implementation decisions.",
"title": "I Built a Production-Ready Spring Boot 4.1.0 SaaS Boilerplate — Here Is What I Learned"
}