# specification-arg-resolver: typed filtering specs for Spring MVC controllers

> A Spring library that turns HTTP query parameters into JPA Specification objects, so a controller method takes a specification argument instead of hand written predicates. It handles joins, case insensitive matching, type conversion, static predicates and Swagger generation.

**tkaczmarzyk/specification-arg-resolver** — An alternative API for filtering data with Spring MVC & Spring Data JPA

- Repository: https://github.com/tkaczmarzyk/specification-arg-resolver
- Stars: 698 · Forks: 155
- Language: Java
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/tkaczmarzyk-specification-arg-resolver

## The problem is that filtering is a query parameter, not a method argument

The library is described as an alternative API for filtering data with Spring MVC and Spring Data JPA, and the blog post linked from the readme is described as a thorough introduction and the original rationale.

The problem it addresses shows up in a very ordinary controller. You have a customers endpoint, and the query string carries a filter, in this case a first name. The conventional way to handle it is to accept the string as a parameter and then build the predicate yourself, either inline or through a repository method. That is fine for one filter and unmanageable for a dozen, because each one is a parameter, a null check, a type conversion and a piece of query construction, repeated in every list endpoint.

The library's answer is to let the framework build the predicate for you. The request is the same:

```
GET http://myhost/api/customers?firstName=Homer
```

and the controller method takes a Specification argument annotated with the parameter name and the filtering strategy:

```java
@RequestMapping(value = "/customers", params = "firstName")
public Iterable<Customer> findByFirstName(  
      @Spec(path = "firstName", spec = Like.class) Specification<Customer> spec) {

    return customerRepo.findAll(spec);
}
```

which results in a JPA query that reads like the request:

```sql
select c from Customer c where c.firstName like '%Homer%'
```

So the filter is now part of the method signature, the type of the filter is explicit, and the repository call is unchanged. The library removes the boilerplate, not the JPA.

## One argument resolver wires the whole thing up

There is a single piece of setup, and it is a standard Spring extension point rather than anything bespoke.

All you need to do is to wire SpecificationArgumentResolver into your application. Then you can use @Spec and other annotations in your controllers. SpecificationArgumentResolver implements Spring's HandlerMethodArgumentResolver and can be plugged in like this:

```java
@Configuration
@EnableJpaRepositories
public class MyConfig implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new SpecificationArgumentResolver());
    }

    ...
}
```

The design choice worth noting is that this is a plain WebMvcConfigurer override with a resolver added to a list. There is no starter, no autoconfiguration and no annotation scanning for the library's own annotations. The consequence is that the library is transparent: you can see exactly where the resolution happens, and a resolver added by another library later would sit alongside it in the same list rather than conflicting with a hidden mechanism.

The resolution itself does the work the readme summarises as converting HTTP parameters, which arrive as strings, into most popular Java types such as dates, numbers, and enums. If you need a conversion the library does not have, the custom converters section covers configuring more.

The readme also links working Spring Boot applications that use the library, one for Spring Boot 3 which includes a demonstration of Native Images for GraalVM, and one for Spring Boot 2. Those example repositories are worth more than the readme for understanding the wiring, because they show the configuration in a real application.

## Property paths, multi value parameters and joining entities

The @Spec annotation has a path property that specifies the property path of the attribute of an entity, for example address.city. By default it is also the name of the expected HTTP parameter, so GET http://myhost?address.city=Springfield does what it looks like.

The spec attribute of the annotation selects the filtering strategy, such as Like, Equal or In, and the readme lists the simple specifications as including Equal, Like, GreaterThan and others.

Multi value filters need their own handling, and there are two ways to pass multiple arguments. The first is passing the same HTTP parameter multiple times:

```
GET http://myhost/customers?gender=MALE&gender=FEMALE
```

The second is the paramSeparator attribute of @Spec, which determines the argument separator and can be specified only for query parameters. That distinction between query parameters and other sources of parameters is the kind of detail that catches people out, because a path variable or a request body cannot carry a separator.

Joining is the feature that decides whether the library is useful to you. The join section covers filtering by attributes of joined entities, and the join fetch section covers initializing lazy associations, which is a separate problem from filtering and a common source of the N plus 1 query pattern. Having both in the same library means a filter on a joined attribute does not force you to also solve the lazy loading separately.

The readme is explicit that the path has to correspond to something real. A path like address.city is a nested property path, and if it does not resolve against your entity model the query fails, so the paths are the contract between your HTTP API and your JPA model.

## Static predicates, defaults and annotated interfaces

Three features extend the basic model beyond a single parameter, and they are the ones that make the library usable for real endpoints rather than demos.

Static parts of queries are predicates that are not bound to HTTP parameters, such as deleted = false. Soft delete is the canonical case, and without a way to express it every endpoint has to either take the flag as a parameter or filter after the fact.

Default value of queries provides a fallback value when the HTTP parameter is not present. Combined with the section on handling non-present HTTP parameters, the two features together give you a defined behaviour for a missing filter, which is the difference between an endpoint that returns everything and one that returns a sensible subset.

Annotated specification interfaces move the declaration off the method. Instead of annotating a parameter, you annotate an interface:

```java
  @Spec(path="firstName", params="name", spec=Like.class)
  public interface NameSpec extends Specification<Customer> {
  }
```

and then use it as a controller parameter without any further annotations. The readme has a section on the interface inheritance tree, which is how you build a hierarchy of filter interfaces, for example a customer interface extending a person interface, and resolve the more specific one when the request asks for it.

The difference in approach between the two styles is worth naming. The parameter annotation is explicit and local, you can see the filter in the method signature. The interface is a named, reusable unit, which is better when several endpoints share the same filter set. The library supports both, and the readme's table of contents lists them as separate approaches rather than one replacing the other.

## Type conversion, case insensitivity, escaping and SpEL

A filtering library is only as good as its handling of the awkward cases, and the readme has a long section on them.

Handling different field types covers the situation when the provided parameter is of a different type than the field, for example an abc string sent against an integer field. The type conversions section covers converting HTTP parameters into Java types such as LocalDateTime, and the support for defining custom converters.

Case insensitivity support describes the strategies used for case-insensitive specifications, and the locale support section is about the Locale configuration for case-insensitive matching. Those two are one feature seen from two sides, because a case insensitive comparison is only well defined once you have decided which locale's rules apply, and Turkish dotted I is the classic example of where an implementation goes wrong.

Character escaping support covers escaping special characters in LIKE based specifications, which is a security feature as much as a correctness one. A user supplied value that reaches a LIKE clause with its wildcards intact changes the shape of the query rather than the value compared, so the escaping has to happen in the library where the clause is built.

SpEL support means Spring Expression Language expressions can be used in specifications, which is the most powerful and the least predictable of the features, since it moves arbitrary expression evaluation into a user supplied string.

Swagger support is the last of the group, and it matters for anyone with a front end team. If the filters are declared through the library's annotations, the generated OpenAPI document can describe them, so the client is not left guessing which query parameters exist.

## Where this library is the wrong choice

There are cases where a specification resolver is overhead rather than saving you anything.

The first is when your endpoints are not parameter driven. Plenty of Spring applications have a handful of carefully written queries for their important screens, and a specification resolver only pays off when the set of possible filters is open ended and driven by the client. If you have twenty endpoints with one hard coded filter each, the library adds a dependency and a configuration class for nothing.

The second is the cost of the abstraction when the query gets complicated. A specification is a composable tree of predicates, and composing them by hand, in the right order, with the right joins, is real work that now happens at request time rather than in code you can read. The library moves the complexity, it does not remove it, and for a query with unusual semantics you will end up debugging generated criteria rather than a query method.

The third is the coupling to your entity model. The path strings in the annotations are property paths, and they are checked at request time rather than compile time, so a rename of an entity field breaks the API at runtime rather than at build time. That is a real cost in a large model, and it is the price of the approach rather than a defect in the implementation.

The fourth is that the library is for JPA. The specification concept is a JPA concept, and if your persistence layer is not Spring Data JPA, there is nothing here to plug into.

And the fifth is the version matrix. The readme has a compatibility notes section, covering older versions compatible with previous Spring Boot and Java versions, and the release history shows parallel lines, with v3.4.0 and v4.1.0 both published on 2026-05-12 and v4.0.0 on 2025-11-26. Two lines moving in parallel means you have to pick the line that matches your Spring Boot generation rather than the newest one.

## Against hand written predicates, and against a query builder DSL

Two comparisons make the trade clear.

The first is against hand written predicates in the controller. The difference in approach is where the type information lives. With the hand written version, the controller method takes a String, the conversion happens in your code, and the predicate is a lambda you wrote. With the library, the controller method takes a Specification, the conversion happens in the resolver, and the predicate is assembled by the library from the annotation. The win is that the endpoint signature documents what can be filtered, and a dozen filters cost a dozen annotations rather than a dozen blocks of conversion code. The loss is that you now have a layer between your request and your query, and a mistake in it surfaces as a query error rather than a compile error.

The second is against a query builder DSL, the kind where you write something like where().eq().and().like(). The difference is that those libraries build a query in your code, so the query is assembled once and can be optimised, cached or reused, whereas a specification resolver assembles it per request from the incoming parameters. For a fixed set of filters that are known at build time, a query builder is the better tool. For filters that arrive from the client at runtime and vary per request, the resolver is the better tool, because the parameter names are the contract and the client can discover them.

That is really the decision rule. Open ended, client driven filtering is what this library is for, and the fact that it also generates Swagger documentation is a sign that the author designed it around an API contract rather than around a query.

## Conclusion

Adopt specification-arg-resolver if your Spring MVC application exposes list endpoints that filter over JPA entities and you want the filtering declared in the controller signature rather than assembled by hand in each method. Skip it if your API is mostly hand written queries, since the library's value is in parameter driven filtering. Verify first that the Spring Boot and Java versions you run match the release line you pick, that your property paths map to real entity attributes, and that the JPA provider you use supports the specifications it generates.

## FAQ

### What does specification-arg-resolver do?

It is an alternative API for filtering data with Spring MVC and Spring Data JPA. It resolves a Specification argument in a controller method from the HTTP request parameters, so a request like GET http://myhost/api/customers?firstName=Homer becomes a JPA query such as select c from Customer c where c.firstName like '%Homer%'.

### How do I enable the specification argument resolver in a Spring app?

Wire SpecificationArgumentResolver into your application by overriding addArgumentResolvers in a WebMvcConfigurer and adding a new SpecificationArgumentResolver to the list. It implements Spring's HandlerMethodArgumentResolver, and after that you can use @Spec and the other annotations in your controllers.

### How do I filter by a field of a joined entity with this library?

The @Spec annotation has a path property specifying the property path of the entity attribute, for example address.city, and by default that path is also the expected HTTP parameter name. There is also a separate join fetch feature for initializing lazy associations.

### Which filtering strategies does specification-arg-resolver support?

The spec attribute of @Spec selects the strategy, and the readme lists simple specifications such as Equal, Like and GreaterThan. Multi value filters like In and NotIn can be given by repeating the HTTP parameter or by using the paramSeparator attribute, which can be specified only for query parameters.

### Does the library handle case insensitive matching and type conversion?

Yes. HTTP parameters arrive as strings and are converted into most popular Java types such as dates, numbers and enums, with custom converters supported. Case insensitivity strategies are documented along with the Locale configuration that makes them well defined, and special characters in LIKE based specifications can be escaped.

### Which versions of specification-arg-resolver exist?

The recent releases are v4.1.0 and v3.4.0, both published on 2026-05-12, and v4.0.0 on 2025-11-26. The readme has a compatibility notes section covering older versions compatible with previous Spring Boot and Java versions, and the last push was on 2026-06-30. The licence is Apache-2.0.

## Sources

- [Issues](https://github.com/tkaczmarzyk/specification-arg-resolver/issues)
- [License: Apache-2.0](https://github.com/tkaczmarzyk/specification-arg-resolver/blob/master/LICENSE)
- [README](https://github.com/tkaczmarzyk/specification-arg-resolver/blob/master/README.md)
- [Releases](https://github.com/tkaczmarzyk/specification-arg-resolver/releases)
- [tkaczmarzyk/specification-arg-resolver on GitHub](https://github.com/tkaczmarzyk/specification-arg-resolver)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tkaczmarzyk-specification-arg-resolver
