From Spring Boot to Django
I’ve spent years in the JVM world, Spring Boot specifically, to the point where rigid layers (controller, service, repository, entity) and the compiler yelling at me before I even run the app just felt like “how backend works”.
Then a job pushed me into Python, Django and Django REST Framework (DRF), and I had to relearn a bunch of stuff I thought I already knew.
This is not a “which one is better” post, I don’t really care about winning that argument. It’s just what changed in my head going from one to the other.
Modular vs batteries-included
Spring makes you build everything piece by piece. Want auth? Add Spring Security. Want persistence? Add Spring Data JPA. Dependency injection glues it all together with @Autowired and @Component. You get full control over every piece, but also a pile of boilerplate and decisions to make before you write any actual business logic.
Django just ships with everything already in the box, auth, ORM, sessions, even a full admin panel, all wired together from day one. Less stuff to decide, way faster to get something running.
Migrations: this is where Django actually got me
This is the part that genuinely changed how I think about databases.
In Spring I’m used to Liquibase or Flyway, you write changesets by hand (XML, YAML, or raw SQL), and you’re responsible for keeping your JPA entities in sync with them yourself. Nothing checks that for you, it’s all manual, and on a big team it’s an easy way to step on each other’s changesets.
A changeset to add a column looks like this:
- changeSet:
id: add-bio-to-user
author: celeste
changes:
- addColumn:
tableName: user
columns:
- column:
name: bio
type: varchar(255)
And separately, by hand, you go update the User entity to match.
In Django you only touch the model:
class User(models.Model):
bio = models.CharField(max_length=255, blank=True)
then run:
python manage.py makemigrations
python manage.py migrate
Django diffs your models against the current schema, writes a migration file you can actually read, and applies it. No hand-written SQL, no code/database drift, no remembering to update two places instead of one.
I didn’t expect a migration tool to be the thing that sold me on a framework, but here we are.
Writing APIs: DTOs and mappers vs serializers
In Spring I usually end up with three things per resource: an Entity, a DTO, and a mapper (MapStruct or hand-rolled) just to move data between the two. Works fine, just verbose.
DRF squashes that into one ModelSerializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'bio']
validation and representation in the same place.
Same story for routing. Spring’s @RestController wants every endpoint and verb spelled out:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping public List<UserDTO> list() { ... }
@GetMapping("/{id}") public UserDTO get(@PathVariable Long id) { ... }
@PostMapping public UserDTO create(@RequestBody UserDTO dto) { ... }
// PUT, PATCH, DELETE...
}
DRF’s ModelViewSet gives you the whole CRUD set from one class:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]
Same result, way less code to maintain.
What I liked
Speed, mostly. Going from nothing to a working authenticated API is way faster than in Spring.
The admin panel is also stupid good, you get a full backoffice for free, in Spring that’s weeks of dedicated frontend/backend work nobody wants to do.
And since auth, ORM and sessions are all native, I never hit the “these two libraries don’t actually like each other” problem that sometimes happens bumping dependencies in Maven/Gradle.
What I didn’t like
Losing compile-time type safety took adjustment. Python being dynamic means stuff Spring would’ve caught before the app even started now surfaces at runtime, so tests and type hints (or mypy) stop being optional and become the thing keeping you sane.
Raw performance and concurrency too, the JVM is built for heavy multithreaded load, Django is tied to WSGI/ASGI and the GIL, so scaling needs extra tooling like Celery instead of just being a given.
And Django really wants its own structure (models.py, views.py, urls.py). If you’re trying to force a hexagonal or Clean Architecture shape onto it, it fights back more than Spring does.
Conclusion
Django isn’t just a toy for small projects, it’s solid enough for real enterprise work too.
If you need to ship fast, manage data, and want a backoffice without building one, Django wins easily. If you need ultra-performant microservices, heavy data processing, or deep integration with old enterprise systems, Spring Boot still has the edge.
I didn’t come out of this picking a side, I just got a second way to look at the same problems, which honestly is worth more than knowing which one is “the best”.