{"record":{"id":"ceb79c97b71a9715","repo":"hibernate/hibernate-orm","slug":"many-to-many-defining-filter-or-where-without-join","errorCode":null,"errorMessage":"many-to-many defining filter or where without join fetching is not valid within collection [%s] using join fetching","messagePattern":"many-to-many defining filter or where without join fetching is not valid within collection \\[(.+?)\\] using join fetching","errorType":"exception","errorClass":"MappingException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java","lineNumber":3215,"sourceCode":"\t\t\t\t\tgetNonEmptyOrConjunctionIfBothNonEmpty(\n\t\t\t\t\t\t\treferencedEntityBinding.getWhere(),\n\t\t\t\t\t\t\telementSource.getWhere()\n\t\t\t\t\t)\n\t\t\t);\n\t\t\tcollectionBinding.setManyToManyOrdering( elementSource.getOrder() );\n\t\t\tbindManyToManyFilters( elementSource, mappingDocument, collectionBinding, elementBinding );\n\t\t}\n\n\t\tprivate void bindManyToManyFilters(\n\t\t\t\tPluralAttributeElementSourceManyToMany elementSource,\n\t\t\t\tMappingDocument mappingDocument,\n\t\t\t\tCollection collectionBinding,\n\t\t\t\tManyToOne elementBinding) {\n\t\t\tif ( !isEmpty( elementSource.getFilterSources() )\n\t\t\t\t\t|| elementSource.getWhere() != null ) {\n\t\t\t\tif ( collectionBinding.getFetchStyle() == FetchStyle.JOIN\n\t\t\t\t\t\t&& elementBinding.getFetchStyle() != FetchStyle.JOIN ) {\n\t\t\t\t\tthrow new MappingException(\n\t\t\t\t\t\t\t\"many-to-many defining filter or where without join fetching is not \"\n\t\t\t\t\t\t\t+ \"valid within collection [%s] using join fetching\"\n\t\t\t\t\t\t\t\t\t.formatted( getPluralAttributeSource().getAttributeRole().getFullPath() ),\n\t\t\t\t\t\t\tmappingDocument.getOrigin()\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor ( var filterSource : elementSource.getFilterSources() ) {\n\t\t\t\tbindManyToManyFilter( mappingDocument, collectionBinding, filterSource );\n\t\t\t}\n\t\t}\n\n\t\tprivate void bindManyToManyFilter(\n\t\t\t\tMappingDocument mappingDocument, Collection collectionBinding, FilterSource filterSource) {\n\t\t\tfinal String name = filterSource.getName();\n\t\t\tif ( name == null ) {\n\t\t\t\tif ( BOOT_LOGGER.isTraceEnabled() ) {","sourceCodeStart":3197,"sourceCodeEnd":3233,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java#L3197-L3233","documentation":"For a many-to-many collection element that declares <filter> elements or a where= restriction, Hibernate must apply that constraint inside the same SQL as the collection load. When the collection binding's fetch style is JOIN but the element (many-to-one) binding resolved to a non-JOIN fetch (typically fetch='select' on the <many-to-many>), the constraint cannot be applied and binding fails.","triggerScenarios":"<bag name='...' fetch='join'><many-to-many class='...' fetch='select' where='...'/></bag>, or a <many-to-many> containing <filter> elements while the collection is join-fetched and the element fetch was overridden to select.","commonSituations":"Tuning fetch strategies per association and overriding the element's fetch to select; adding row-level filters/where clauses to existing join-fetched collections; copying filter definitions from select-fetched sets into join-fetched bags.","solutions":["Remove fetch='select' from the <many-to-many> element so it follows the collection's join fetching","Or switch the collection itself to fetch='select' (or default) so the filter/where can be applied in a separate select","Or drop the where/filters on the element if join fetching the collection matters more"],"exampleFix":"// before\n<set name='roles' fetch='join'>\n    <key column='user_id'/>\n    <many-to-many class='Role' fetch='select' where=\"deleted = false\"/>\n</set>\n\n// after\n<set name='roles' fetch='join'>\n    <key column='user_id'/>\n    <many-to-many class='Role' where=\"deleted = false\"/>\n</set>","handlingStrategy":"validation","validationCode":"NodeList m2ms = doc.getElementsByTagName(\"many-to-many\");\nfor (int i = 0; i < m2ms.getLength(); i++) {\n    Element m = (Element) m2ms.item(i);\n    boolean hasFilterOrWhere = m.getElementsByTagName(\"filter\").getLength() > 0 || m.getAttributeNode(\"where\") != null;\n    Element collection = (Element) m.getParentNode();\n    boolean collectionJoin = \"join\".equals(collection.getAttribute(\"fetch\"));\n    boolean elementSelect = \"select\".equals(m.getAttribute(\"fetch\"));\n    if (hasFilterOrWhere && collectionJoin && elementSelect) {\n        throw new IllegalStateException(\"filtered many-to-many inside join-fetched collection must not use fetch='select'\");\n    }\n}","typeGuard":null,"tryCatchPattern":"catch (MappingException e) at bootstrap; the message names the collection. Remove fetch='select' from the many-to-many or switch the collection off join fetching so the filter/where can be applied.","preventionTips":["Do not override the element fetch to select inside join-fetched collections that use filters/where","Decide fetch strategy at the collection level when row filters are involved","Test filtered collections against real SQL during development"],"tags":["hibernate","hbm-mapping","many-to-many","fetch-strategy","filter"],"backgroundTag":"fetch-strategy-misconfiguration","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}