From Querydsl to Spring Filter: One Syntax, Three Backends
Querydsl is one of those libraries that everyone used for years and then quietly stopped updating. The 5.0 release has been "coming soon" since 2019. The GitHub shows commits but no milestone. The issue tracker has a thread titled "Is Querydsl dead?" with hundreds of comments. It's not dead. But if you're starting a new project in 2026 and you're picking between Querydsl and something that's actively maintained, has Spring Boot 4 support, works with MongoDB and in-memory collections, generates OpenAPI docs automatically, and has companion frontend libraries... well, you see where I'm going. This isn't a "Querydsl bad, Spring Filter good" article. Querydsl pioneered type-safe querying for Java and it deserves credit. But migrations happen, and if you're considering one, here's what the conversion looks like. Side-by-side: basic filtering Querydsl: QCar car = QCar.car; BooleanExpression filter = car.year.gt(2020) .and(car.km.lt(50000)) .and(car.color.eq(Color.RED)); List results = new JPAQuery(entityManager) .select(car) .from(car) .where(filter) .fetch(); Spring Filter (query string): @Filter Specification spec // URL: ?filter=year > 2020 and km < 50000 and color : 'red' List results = carRepo.findAll(spec); Spring Filter (programmatic builder): FilterNode filter = fb.field("year").greaterThan(fb.input(2020)) .and(fb.field("km").lessThan(fb.input(50000))) .and(fb.field("color").equal(fb.input(Color.RED))) .get(); Specification spec = converter.convert(filter); List results = carRepo.findAll(spec); Spring Filter (type-safe builder): FilterNode f = CarFilter.where(fb) .year().greaterThan(2020) .and() .km().lessThan(50000) .and() .color().equal(Color.RED) .build(); Specification spec = converter.convert(f); List results = carRepo.findAll(spec); The type-safe builder reads almost exactly like Querydsl. The difference is that CarFilter is generated from your JPA entity via annotation processor, not from a separate Q-class that needs its own build step. What Querydsl does that Spring Filter doesn't Gotta be honest about this part. 1. SQL-level joins and subqueries. Querydsl can express SELECT * FROM cars WHERE brand_id IN (SELECT id FROM brands WHERE country = 'DE'). Spring Filter doesn't generate subqueries. It handles entity relation traversal (brand.name : 'audi') which generates a JOIN under the hood, but complex subquery logic isn't in scope. If your query needs EXISTS (SELECT ...), you'll still need CriteriaBuilder or native SQL for that part. 2. Update and delete queries. Querydsl has new JPAUpdateClause() and new JPADeleteClause(). Spring Filter is read-only. It generates WHERE clauses, not DML. If you need to bulk-update filtered records, you apply the generated Specification to find them, then update manually. Not as clean. 3. Tuple projections without mapping to DTOs. Querydsl lets you select arbitrary expressions into Tuple objects. Spring Filter doesn't do projections at all -- it only generates WHERE clauses. Your SELECT is handled by Spring Data or your own CriteriaQuery setup. You can use Spring Filter's Specification in a projection query (the README shows how), but the projection itself is still CriteriaBuilder code. 4. Maturity of the type system. Querydsl's APT processor handles generics, wildcards, and complex type hierarchies better than Spring Filter's @Filterable processor. If your entities use Map
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to