{"id":18737417,"url":"https://github.com/mikesafonov/spring-boot-starter-specification-builder","last_synced_at":"2025-08-12T06:34:17.155Z","repository":{"id":37860269,"uuid":"211505267","full_name":"MikeSafonov/spring-boot-starter-specification-builder","owner":"MikeSafonov","description":"Spring Boot starter for building specifications in declarative way","archived":false,"fork":false,"pushed_at":"2023-03-06T13:00:09.000Z","size":235,"stargazers_count":11,"open_issues_count":7,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-01T18:37:53.180Z","etag":null,"topics":["java","spring-boot","spring-data-jpa","spring-data-jpa-specification"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MikeSafonov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-09-28T13:32:39.000Z","updated_at":"2025-01-12T20:43:28.000Z","dependencies_parsed_at":"2023-02-18T06:46:03.956Z","dependency_job_id":"5451d84d-b4f9-446c-8e44-58554a648f82","html_url":"https://github.com/MikeSafonov/spring-boot-starter-specification-builder","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/MikeSafonov/spring-boot-starter-specification-builder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MikeSafonov%2Fspring-boot-starter-specification-builder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MikeSafonov%2Fspring-boot-starter-specification-builder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MikeSafonov%2Fspring-boot-starter-specification-builder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MikeSafonov%2Fspring-boot-starter-specification-builder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MikeSafonov","download_url":"https://codeload.github.com/MikeSafonov/spring-boot-starter-specification-builder/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MikeSafonov%2Fspring-boot-starter-specification-builder/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270014419,"owners_count":24512649,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-12T02:00:09.011Z","response_time":80,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","spring-boot","spring-data-jpa","spring-data-jpa-specification"],"created_at":"2024-11-07T15:25:04.151Z","updated_at":"2025-08-12T06:34:17.110Z","avatar_url":"https://github.com/MikeSafonov.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# spring-boot-starter-specification-builder\n[![Maven Central](https://img.shields.io/maven-central/v/com.github.mikesafonov/spring-boot-starter-specification-builder.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.github.mikesafonov%22%20AND%20a:%22spring-boot-starter-specification-builder%22)\n[![codecov](https://codecov.io/gh/MikeSafonov/spring-boot-starter-specification-builder/branch/master/graph/badge.svg)](https://codecov.io/gh/MikeSafonov/spring-boot-starter-specification-builder)\n[![Travis-CI](https://travis-ci.com/MikeSafonov/spring-boot-starter-specification-builder.svg?branch=master)](https://travis-ci.com/MikeSafonov/spring-boot-starter-specification-builder)\n[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)\n\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=MikeSafonov_spring-boot-starter-specification-builder\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=MikeSafonov_spring-boot-starter-specification-builder)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=MikeSafonov_spring-boot-starter-specification-builder\u0026metric=reliability_rating)](https://sonarcloud.io/dashboard?id=MikeSafonov_spring-boot-starter-specification-builder)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=MikeSafonov_spring-boot-starter-specification-builder\u0026metric=sqale_rating)](https://sonarcloud.io/dashboard?id=MikeSafonov_spring-boot-starter-specification-builder)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=MikeSafonov_spring-boot-starter-specification-builder\u0026metric=security_rating)](https://sonarcloud.io/dashboard?id=MikeSafonov_spring-boot-starter-specification-builder)\n\n[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=MikeSafonov_spring-boot-starter-specification-builder\u0026metric=bugs)](https://sonarcloud.io/dashboard?id=MikeSafonov_spring-boot-starter-specification-builder)\n[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=MikeSafonov_spring-boot-starter-specification-builder\u0026metric=code_smells)](https://sonarcloud.io/dashboard?id=MikeSafonov_spring-boot-starter-specification-builder)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=MikeSafonov_spring-boot-starter-specification-builder\u0026metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=MikeSafonov_spring-boot-starter-specification-builder)\n\n[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=MikeSafonov_spring-boot-starter-specification-builder\u0026metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=MikeSafonov_spring-boot-starter-specification-builder)\n[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=MikeSafonov_spring-boot-starter-specification-builder\u0026metric=ncloc)](https://sonarcloud.io/dashboard?id=MikeSafonov_spring-boot-starter-specification-builder)\n[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=MikeSafonov_spring-boot-starter-specification-builder\u0026metric=sqale_index)](https://sonarcloud.io/dashboard?id=MikeSafonov_spring-boot-starter-specification-builder)\n\nThis is a spring Boot starter for building specifications in declarative way.\n\nThe starter is available at `maven central` repository.\n\nUsing `gradle`: \n    \n    dependencies {\n        implementation 'com.github.mikesafonov:spring-boot-starter-specification-builder:version'\n    }\n\nUsing `maven`:\n\n    \u003cdependency\u003e\n      \u003cgroupId\u003ecom.github.mikesafonov\u003c/groupId\u003e\n      \u003cartifactId\u003espring-boot-starter-specification-builder\u003c/artifactId\u003e\n      \u003cversion\u003eversion\u003c/version\u003e\n    \u003c/dependency\u003e\n\n## Usage\n\nOnly one bean ([SpecificationBuilder](https://github.com/MikeSafonov/spring-boot-starter-specification-builder/blob/master/src/main/java/com/github/mikesafonov/specification/builder/starter/SpecificationBuilder.java)) is created automatically by this starter.\n\nIn all of the examples below, it is assumed that we have the following classes:\n\nEntity:\n```java\n@Entity\n@Table(name = \"my_entity\")\npublic class MyEntity {\n    ...\n}\n```\n\nFilter:\n```java\npublic class MyEntityFilter {\n    ...\n}\n```\n\nRepository:\n```java\npublic interface MyEntityRepository extends JpaRepository\u003cMyEntity, Integer\u003e, JpaSpecificationExecutor\u003cMyEntity\u003e {\n}\n```\n\nService which uses MyEntityRepository and `SpecificationBuilder` \n\n```java\npublic class MyEntityService {\n    ...\n    public List\u003cMyEntity\u003e findByFilter(MyEntityFilter filter){\n        return myEntityRepository.findAll(specificationBuilder.buildSpecification(filter));\n    }   \n}\n```\n\n### Get all entities with specific field `is equals` to filters value\n\nThe following code example demonstrates how to find all entities by filter`s value:\n\nEntity:\n```java\n@Entity\n@Table(name = \"car_models\")\npublic class CarModel {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"name\")\n    private String name;\n}\n```\n\nFilter:\n```java\npublic class CarFilter {\n    private String name;\n}\n```\n\nIf there is no annotation on the filters field then `SpecificationBuilder` create `equals` predicate.\n\n### Different `name` of column in filter\n\nThe following code example demonstrates how to link filters field with entity`s field:  \n\nEntity:\n```java\n@Entity\n@Table(name = \"car_models\")\npublic class CarModel {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"name\")\n    private String name;\n}\n```\n\nFilter:\n```java\npublic class CarFilter {\n    @Name(value = \"name\")\n    private String filterByName;\n}\n```\n\n### Filter by several columns by one expression\n\nThe following code example demonstrates how to filter by several columns by one expression:\n\nEntity:\n```java\n@Entity\n@Table(name = \"cars\")\npublic class CarEntity {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"number\")\n    private String number;\n    @Column(name = \"cost_from\")\n    private int costFrom;\n    @Column(name = \"cost_to\")\n    private int costTo;\n    @ManyToOne\n    @JoinColumn(name = \"id_model\")\n    private CarModel model;\n}\n```\n\nFilter:\n```java\n@Data\npublic class CostGreaterThenCarFilter {\n    @GreaterThan\n    @Names(value = {\"costFrom\", \"costTo\"}, type = Names.SearchType.AND)\n    private Integer value;\n}\n```\n\n### Ignore specific field in filter\n\nThe following code example demonstrates how to ignore specific field in filter:\n\nEntity:\n```java\n@Entity\n@Table(name = \"car_models\")\npublic class CarModel {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"name\")\n    private String name;\n}\n```\n\nFilter:\n```java\npublic class CarFilter {\n    @Ignore\n    private String name;\n    @Name(value = \"name\")\n    private String filterByName;\n}\n```\nIn this example `SpecificationBuilder` create `equals` predicate only by `filterByName` field.\n\n### Get all entities with specific field `is not null`\n\nThe following code example demonstrates how to find all entities with specific field is not null:\n\nEntity:\n```java\n@Entity\n@Table(name = \"car_models\")\npublic class CarModel {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"name\")\n    private String name;\n}\n```\n\nFilter:\n```java\npublic class CarFilter {\n    @NonNull\n    @Name(value = \"name\")\n    private String filterByName;\n}\n```\n\n### Get all entities with specific field `is null`\n\nThe following code example demonstrates how to find all entities with specific field is null:\n\nEntity:\n```java\n@Entity\n@Table(name = \"car_models\")\npublic class CarModel {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"name\")\n    private String name;\n}\n```\n\nFilter:\n```java\npublic class CarFilter {\n    @IsNull\n    @Name(value = \"name\")\n    private String filterByName;\n}\n```\n\n### Get all entities with specific field `greater than`/`greater than or equals`/`less than`/`less than or equals` filters value\n\nThe following code example demonstrates how to use `@GreaterThan`, `@GreaterThanEqual`, `@LessThan` and `@LessThanEqual` annotations:\n\nFilter:\n```java\npublic class CarFilter {\n    @GreaterThan\n    @Name(value = \"size\")\n    private Double filterSize;\n    @GreaterThanEqual\n    @Name(value = \"size2\")\n    private Double filterSize2;\n    @LessThan\n    @Name(value = \"size3\")\n    private Double filterSize3;\n    @LessThanEqual\n    @Name(value = \"size4\")\n    private Double filterSize4;\n}\n```\n\n### `Like` predicate\n\n`Like` predicate works only with `String` values.\n\nThe following code example demonstrates how to find all entities with specific field is `like` to filters value\n\nEntity:\n```java\n@Entity\n@Table(name = \"car_models\")\npublic class CarModel {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"name\")\n    private String name;\n}\n```\n\nFilter:\n```java\npublic class CarFilter {\n    @Like\n    @Name(value = \"size\")\n    private String likeName;\n}\n```\n\nBy default `@Like` ignores case. If you want to search by case sensitive values use `caseSensitive` property:\n\n```java\npublic class CarFilter {\n    @Like(caseSensitive = true)\n    @Name(value = \"size\")\n    private String likeName;\n}\n```\n\nBy default `@Like` search by `full` like (%value%). If you want to search by `left` like or `right` use `direction` property:\n\n```java\npublic class CarFilter {\n    @Like(direction = Like.DIRECTION.LEFT)\n    @Name(value = \"size\")\n    private String likeName;\n}\n```\n\n```java\npublic class CarFilter {\n    @Like(direction = Like.DIRECTION.RIGHT)\n    @Name(value = \"size\")\n    private String likeName;\n}\n```\n\nAlso, you can use `@Like` without `%` bounds\n\n```@Like(direction = Like.DIRECTION.NONE)```\n\n### Using `Join`\n\nThe following code example demonstrates how to find all entities with specific field is `equal` to filters value, joined\nby another field\n\nEntities:\n\n```java\n\n@Entity\n@Table(name = \"car_models\")\npublic class CarModel {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"name\")\n    private String name;\n}\n```\n\n```java\n@Entity\n@Table(name = \"cars\")\npublic class Car {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"name\")\n    private String name;\n    @ManyToOne\n    @JoinColumn(name = \"id_model\")\n    private CarModel model;\n}\n```\n\nFilter:\n```java\npublic class CarFilter {\n    @Join(value = \"model\")\n    @Name(value = \"name\")\n    private String model;\n\n    @Join(value = \"model\")\n    @Name(value = \"id\")\n    @GreaterThan\n    private Integer modelId;\n}\n```\n\n`Join` is `Repeatable` annotation so you can join multiple entities.\n\n### Collections in filter\n\nThe following code example demonstrates how to find all entities with specific field `contains in` filters value:\n\nEntity:\n ```java\n @Entity\n @Table(name = \"car_models\")\n public class CarModel {\n     @Id\n     @GeneratedValue(strategy = GenerationType.IDENTITY)\n     private Integer id;\n     @Column(name = \"name\")\n     private String name;\n }\n ```\n \nFilter:\n ```java\n public class CarFilter {\n     @Name(value = \"name\")\n     private Collection\u003cString\u003e names;\n }\n ```\n\n### Filter by `many-to-many` linked tables\n\nThe following code example demonstrates how to find all entities with specific many-to-many joined field `contains in` filters value:\n\nEntity:\n```java\n@Entity\n@Table(name = \"students\")\npublic class StudentEntity {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"name\")\n    private String name;\n    @ManyToMany(fetch = FetchType.EAGER)\n    @JoinTable(name = \"students_classes\",\n            joinColumns = @JoinColumn(name = \"id_student\"),\n            inverseJoinColumns = @JoinColumn(name = \"id_class\"))\n    private Set\u003cClassEntity\u003e classEntities;\n}\n```  \n```java\n@Entity\n@Table(name = \"classes\")\npublic class ClassEntity {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"name\")\n    private String name;\n}\n```\n\nFilter:\n\n```java\npublic class StudentFilter {\n    @ManyToManyCollection\n    @Join(value = \"classEntities\")\n    @Name(value = \"name\")\n    private List\u003cString\u003e classes;\n}\n```\n\n### Filter by segment intersection\n\nThe following code example demonstrate how to find entities by segment bounded by fields like `start` and `end`\nand intersect with segment from filter.\n\nEntity:\n```java\n@Entity\n@Table(name = \"students\")\npublic class StudentEntity {\n    @Column(name = \"date_start_studying\")\n    private LocalDate studyingDateStart;\n\n    @Column(name = \"date_end_studying\")\n    private LocalDate studyingDateEnd;\n}\n```\n\nFilter:\n\n```java\npublic class StudentStudyingFilter {\n    @SegmentIntersection(fromField = \"studyingDateStart\", toField = \"studyingDateEnd\")\n    private SegmentFilter\u003cLocalDate\u003e periodFilter;\n    \n    public StudentStudyingFilter(LocalDate from, LocalDate to) {\n        this.periodFilter = new SegmentFilter\u003c\u003e(from, to);\n    }\n}\n```\n\n`SegmentFilter` - special type from `com.github.mikesafonov.specification.builder.starter.type` \ncontaining temporal `from` and `to`.\n\nYou can use `@Join` annotation with `@SegmentIntersection` to refer to referenced entity.\n\nSegment intersection predicate allow `null` values in `from` and `to` inside `SegmentFilter`,\nand `null` value in entity `toField` what mean not ended segment.\n\n### `Not` predicate\n\n`Not` predicate negates another predicate on field.\n\nThe following code example demonstrates how to find all entities exclude specific value in field.\n\nEntity:\n```java\n@Entity\n@Table(name = \"car_models\")\npublic class CarModel {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"name\")\n    private String name;\n}\n```\n\nFilter:\n```java\npublic class CarFilter {\n    @Not\n    private String name;\n}\n```\n### Use `Distinct`\n\nSpecify whether duplicate query results will be eliminated.\n\n\nEntities:\n```java\n@Entity\n@Table(name = \"clients\")\npublic class ClientEntity {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @Column\n    private String name;\n\n    @OneToMany(fetch = FetchType.LAZY)\n    @JoinColumn(name = \"id_client\")\n    private List\u003cContractEntity\u003e contracts = new ArrayList\u003c\u003e();\n}\n```\n\n```java\n@Entity\n@Table(name = \"contracts\")\npublic class ContractEntity {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @Column\n    private String number;\n\n    @Column(name = \"id_client\")\n    private Long idClient;\n}\n```\n\nFilter:\n```java\n@Distinct\npublic class ClientContractNumberFilter {\n\n    @Like\n    @Join(\"contracts\")\n    @Name(\"number\")\n    private String contract;\n}\n```\n\n### Function wrapping\n\n`Function` annotation allow wrap entity field or/and filter value with function. It can be used with another predicate annotation.\n\nEntity:\n```java\n@Entity\n@Table(name = \"car_models\")\npublic class CarModel {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n    @Column(name = \"name\")\n    private String name;\n}\n```\n\nFilter:\n```java\npublic class CarFilter {\n    @Function(name = \"LOWER\", wrapping = Function.FunctionWrapping.FILTER)\n    @Like\n    private String name;\n}\n```\n\n## Debug\n\nYou may turn on additional logging to debug created predicates:\n\n    logging:\n      level:\n        com:\n          github:\n            mikesafonov:\n              specification:\n                builder:\n                  starter: trace\n\n## Build\n\n### Build from source\n\nYou can build application using following command:\n\n    ./gradlew clean build -x signArchives\n    \n#### Requirements:\n\nJDK \u003e= 1.8\n\n### Unit tests\n\nYou can run unit tests using following command:\n\n    ./gradlew test\n\n## Contributing\n\nFeel free to contribute. \nNew feature proposals and bug fixes should be submitted as GitHub pull requests. \nFork the repository on GitHub, prepare your change on your forked copy, and submit a pull request.\n\n**IMPORTANT!**\n\u003eBefore contributing please read about [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.2/) / [Conventional Commits RU](https://www.conventionalcommits.org/ru/v1.0.0-beta.2/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikesafonov%2Fspring-boot-starter-specification-builder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmikesafonov%2Fspring-boot-starter-specification-builder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikesafonov%2Fspring-boot-starter-specification-builder/lists"}