Why I Built This
Every Spring Boot project I started, I spent the first 2–3 weeks building the exact same things:
- JWT authentication
- Email verification
- Forgot password flow
- Google OAuth2 integration
- Docker setup
- CI/CD pipeline
That is weeks of work before writing a single line of my actual product.
So I packaged all of it into SpringLaunch API — a production-ready Spring Boot 4.1.0 boilerplate.
Here are the biggest lessons I learned while building it.
Spring Boot 4 Changed A Lot
Spring Boot 4.1.0 (released June 2026) is a major release.
Several APIs changed compared to Boot 3.
Test packages moved
// Spring Boot 3
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
// Spring Boot 4
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
@MockBean became @MockitoBean
// Spring Boot 3
@MockBean
private JwtService jwtService;
// Spring Boot 4
@MockitoBean
private JwtService jwtService;
DaoAuthenticationProvider is now auto-configured
If your application exposes both:
UserDetailsServicePasswordEncoder
Spring Security automatically creates the DaoAuthenticationProvider.
No manual bean configuration is required anymore.
JWT Strategy — Two Tokens, Two Places
I use a hybrid authentication strategy.
| Token | Lifetime | Storage |
|---|---|---|
| Access Token | 15 minutes | JSON response body |
| Refresh Token | 7 days | HTTP-only Cookie |
Access token:
public record AuthResponse(
String accessToken,
UserResponse user
) {}
Refresh token:
ResponseCookie cookie = ResponseCookie.from("refreshToken", token)
.httpOnly(true)
.secure(true)
.sameSite("Lax")
.path("/")
.maxAge(maxAgeSeconds)
.build();
Why HTTP-only cookies?
JavaScript cannot read them.
Even if an XSS attack executes on the page, it cannot steal the refresh token.
The @Async Self-Invocation Trap
This bug cost me over an hour.
Calling an @Async method from the same class bypasses Spring's proxy, so the method executes synchronously.
❌ Wrong
@Service
public class AuthServiceImpl {
@Async
private void sendEmail() { }
public void register() {
sendEmail();
}
}
✅ Correct
@Service
public class EmailService {
@Async
public void sendEmail() { }
}
@Service
public class AuthServiceImpl {
private final EmailService emailService;
public void register() {
emailService.sendEmail();
}
}
Because the call goes through Spring's proxy, it becomes truly asynchronous.
Argon2 — Use Password4j
Spring Security's Argon2PasswordEncoder is soft-deprecated.
Instead, Boot 4 recommends Password4j integration.
❌ Old
new Argon2PasswordEncoder(
16,
32,
1,
65536,
3
);
✅ Recommended
import org.springframework.security.crypto.password4j.Argon2Password4jPasswordEncoder;
new Argon2Password4jPasswordEncoder();
Cleaner code with recommended defaults.
Factory Methods On JPA Entities
JPA entities cannot be records because they require:
- mutable fields
- a no-argument constructor
Instead, I use factory methods.
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends BaseEntity implements UserDetails {
public static User ofLocal(
String name,
String username,
String email,
String encodedPassword
) {
User user = new User();
user.name = name.trim();
user.username = username.toLowerCase();
user.email = email.toLowerCase();
user.password = encodedPassword;
user.role = UserRole.USER;
user.provider = AuthProvider.LOCAL;
user.emailVerified = false;
return user;
}
public static User ofGoogle(
String name,
String username,
String email,
String providerId
) {
User user = new User();
// ...
user.emailVerified = true;
return user;
}
}
No one can accidentally create an invalid user.
API Versioning From Day One
Every endpoint is versioned.
public final class ApiVersion {
public static final String V1 = "/v1";
private ApiVersion() {}
}
Controllers simply use:
@RequestMapping(ApiVersion.V1 + "/auth")
public class AuthController {
}
When breaking changes arrive:
- Add
/v2 - Keep
/v1 - Existing clients never break
What Is Included
SpringLaunch API currently includes:
- Spring Boot 4.1.0
- Java 21
- JWT Authentication
- Google OAuth2 Login
- Email Verification
- Password Reset
- 17 REST Endpoints
- 42 Automated Tests
- Docker Compose
- GitHub Actions CI
- Render Deployment Configuration
- 7 Documentation Guides
Final Thoughts
Building this taught me far more than just authentication.
I learned how Spring Boot 4 changed testing, security, password encoding, asynchronous execution, and application architecture.
Most importantly, I now have a production-ready starting point that saves weeks every time I build a new SaaS.
If you're interested, you can check out SpringLaunch API here:
Landing Page
https://sujankim.github.io/springlaunch
It's also available on Gumroad.
I'd love to hear your thoughts or answer any questions about the implementation decisions.
Top comments (0)