I spent an entire day rewriting my frontend code.
Not because I was adding new features. Not because I was fixing bugs.
But because I had updated some entities and endpoints on my Spring Boot backend.
Every TypeScript interface had to be manually rewritten. Every API call had to be updated. Every type had to match the new DTOs.
Hours of mind-numbing work. And the worst part? I knew I'd have to do it all over again the next time I touched the backend.
The Problem Every Full-Stack Developer Knows
If you work with Spring Boot and TypeScript, you know this pain:
- You update a Spring controller
- You update the corresponding DTOs
- Now your frontend is out of sync
- You manually create TypeScript interfaces
- You manually write new fetch calls
- You pray you didn't miss anything
- You get runtime errors because you DID miss something
There had to be a better way.
The Solution: Direct Code Generation
I decided to build a tool that would read my Spring controllers directly and generate TypeScript clients automatically.
No Swagger setup. No OpenAPI specs. No intermediate JSON files.
Just point it at your Spring Boot project and get type-safe TypeScript code.
Here's What It Looks Like
Your Spring Controller:
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/login")
public AuthResponse login(@RequestBody LoginDTO loginDTO) {
return authService.login(
loginDTO.getUsername(),
loginDTO.getPassword()
);
}
@GetMapping("/info")
public User getUserInfo() {
return userService.getUserByUsername(
SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal()
.toString()
).orElseThrow();
}
}
Auto-Generated TypeScript:
import axios from 'axios';
import type { User } from './User';
import type { AuthResponse } from './AuthResponse';
import type { LoginDTO } from './LoginDTO';
export const login = (loginDTO: LoginDTO): Promise<AuthResponse> =>
axios.post(`/user/login`, loginDTO)
.then(response => response.data)
.catch(error => { throw error });
export const getUserInfo = (): Promise<User> =>
axios.get(`/user/info`)
.then(response => response.data)
.catch(error => { throw error });
// Interfaces generated too
export interface LoginDTO {
username: string;
password: string;
}
export interface AuthResponse {
authenticationToken: string;
}
export interface User {
username: string;
roles: Array<RoleType>;
}
export enum RoleType {
ADMIN = "ADMIN",
USER = "USER",
}
That's it. Update your backend? Run it again. Everything stays in sync.
What It Supports
The tool handles all the Spring annotations you'd expect:
@RequestBody@PathVariable@RequestParamPageable@JsonIgnore@Transient- Enums
- Generic types
- Complex nested objects
Why Not Just Use Swagger?
Fair question. There are tools like swagger-typescript-api that generate TypeScript clients from OpenAPI specs.
But here's the thing:
- Setup overhead - You need to configure Swagger/Springdoc first
- Extra dependencies - More stuff in your pom.xml/build.gradle
- Two-step process - Generate Swagger docs, THEN generate TypeScript
- Maintenance - Keep your Swagger annotations up to date
For enterprise projects with mature API documentation practices? Sure, Swagger makes sense.
But for MVPs, internal tools, and small teams that just want to build fast? Direct generation from Spring controllers is simpler.
The Results
I've been using this on every project since I built it.
I can't even remember the last time I had a type mismatch between my frontend and backend. Or a runtime error from an outdated API call.
Update the backend, run one command, move on with my life.
How It Works (Technical Overview)
Under the hood, the tool:
- Scans your Spring Boot application for
@RestControllerclasses - Analyzes method signatures, annotations, and return types
- Traverses the object graph to find all DTOs and entities
- Maps Java types to TypeScript equivalents
- Generates interfaces and Axios functions
- Optionally commits everything to Git automatically
It integrates directly into your Spring Boot startup as a CommandLineRunner:
@Bean
public CommandLineRunner start(
final RequestMappingHandlerMapping requestMappingHandlerMapping
) {
return (args -> {
final Spring2TSModule spring2TSModule = new Spring2TSModule(
List.of("com.yourpackage"),
"output/ts/"
);
spring2TSModule.generate(requestMappingHandlerMapping);
});
}
Run your Spring app once, get all your TypeScript files.
Who This Is For
This tool is perfect if you:
- Work with Spring Boot + TypeScript/React/Vue/Angular
- Are tired of manually syncing frontend types with backend changes
- Want type safety without the Swagger setup overhead
- Build MVPs or internal tools that need to move fast
- Value developer experience and automation
Try It Yourself
I'm making this available to other developers who face the same pain I did.
You can grab it here: https://thomasberrens.gumroad.com/l/spring2ts
It's a one-time payment with lifetime updates. If it saves you even one hour of manual type-syncing, it's worth it.
Also launching on Product Hunt tomorrow if you want to check it out there and provide feedback!
Have you dealt with this problem? What's your solution? Drop a comment below - I'd love to hear how other full-stack devs handle the Spring/TypeScript sync challenge.
Top comments (0)