{"record":{"id":"597efbe6e08e6d02","repo":"hibernate/hibernate-orm","slug":"collection-is-the-unowned-side-of-a-bidirecti","errorCode":null,"errorMessage":"Collection '{}' is the unowned side of a bidirectional '@ManyToMany' and may not have an '@OrderColumn'","messagePattern":"Collection '(.+?)' is the unowned side of a bidirectional '@ManyToMany' and may not have an '@OrderColumn'","errorType":"exception","errorClass":"AnnotationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java","lineNumber":449,"sourceCode":"\t\t\tElementCollection elementCollection) {\n\t\tif ( ( oneToMany != null || manyToMany != null || elementCollection != null )\n\t\t\t\t&& isToManyAssociationWithinEmbeddableCollection( propertyHolder ) ) {\n\t\t\tthrow new AnnotationException( \"Property '\" + getPath( propertyHolder, inferredData ) +\n\t\t\t\t\t\"' belongs to an '@Embeddable' class that is contained in an '@ElementCollection' and may not be a \"\n\t\t\t\t\t+ annotationName( oneToMany, manyToMany, elementCollection ));\n\t\t}\n\n\t\tif ( oneToMany != null && property.hasDirectAnnotationUsage( SoftDelete.class ) ) {\n\t\t\tthrow new UnsupportedMappingException(\n\t\t\t\t\t\"@SoftDelete cannot be applied to @OneToMany - \" +\n\t\t\t\t\t\t\tproperty.getDeclaringType().getName() + \".\" + property.getName()\n\t\t\t);\n\t\t}\n\n\t\tif ( property.hasDirectAnnotationUsage( OrderColumn.class )\n\t\t\t\t&& manyToMany != null\n\t\t\t\t&& isNotBlank( manyToMany.mappedBy() ) ) {\n\t\t\tthrow new AnnotationException(\"Collection '\" + getPath( propertyHolder, inferredData ) +\n\t\t\t\t\t\"' is the unowned side of a bidirectional '@ManyToMany' and may not have an '@OrderColumn'\");\n\t\t}\n\n\t\tif ( manyToMany != null || elementCollection != null ) {\n\t\t\tif ( property.hasDirectAnnotationUsage( JoinColumn.class )\n\t\t\t\t\t|| property.hasDirectAnnotationUsage( JoinColumns.class ) ) {\n\t\t\t\tthrow new AnnotationException( \"Property '\" + getPath( propertyHolder, inferredData )\n\t\t\t\t\t\t+ \"' is a \" + annotationName( oneToMany, manyToMany, elementCollection )\n\t\t\t\t\t\t+ \" and is directly annotated '@JoinColumn'\"\n\t\t\t\t\t\t+ \" (specify '@JoinColumn' inside '@JoinTable' or '@CollectionTable')\" );\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static String annotationName(\n\t\t\tOneToMany oneToMany,\n\t\t\tManyToMany manyToMany,\n\t\t\tElementCollection elementCollection) {","sourceCodeStart":431,"sourceCodeEnd":467,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java#L431-L467","documentation":"On the unowned (mappedBy) side of a bidirectional @ManyToMany, the join and its ordering data live in the owning side's join table, so an @OrderColumn there has no column to map. CollectionBinder.checkAnnotations throws this AnnotationException when @OrderColumn is present and manyToMany.mappedBy() is non-blank.","triggerScenarios":"A property carries @ManyToMany(mappedBy = \"...\") together with @OrderColumn (directly or via hasDirectAnnotationUsage) during checkAnnotations.","commonSituations":"Adding @OrderColumn to both sides of a bidirectional many-to-many for consistent ordering; copy-pasting the owning side's annotations to the inverse side; migrating a unidirectional ordered many-to-many to bidirectional without cleaning up.","solutions":["Remove @OrderColumn from the mappedBy side and keep it on the owning side","Or drop mappedBy on this side and make it the owner with @JoinTable + @OrderColumn (then clean up the other side)","If you need ordered iteration on the inverse side, sort in queries instead of mapping an index"],"exampleFix":"// before\n@Entity\nclass Course {\n    @ManyToMany(mappedBy = \"courses\")\n    @OrderColumn                // error: unowned side may not have @OrderColumn\n    List<Student> students;\n}\n\n// after\n@Entity\nclass Course {\n    @ManyToMany(mappedBy = \"courses\")\n    List<Student> students;     // ordering handled by the owning side\n}\n\n@Entity\nclass Student {\n    @ManyToMany\n    @JoinTable(name = \"student_course\")\n    @OrderColumn\n    List<Course> courses;       // owner carries the order column\n}","handlingStrategy":"validation","validationCode":"// Reject @OrderColumn on the mappedBy side of a @ManyToMany before boot\nstatic void checkOrderColumnOwnership(Class<?>... entities) {\n    for ( Class<?> c : entities ) {\n        for ( Field f : c.getDeclaredFields() ) {\n            ManyToMany m2m = f.getAnnotation( ManyToMany.class );\n            if ( f.isAnnotationPresent( OrderColumn.class )\n                    && m2m != null && !m2m.mappedBy().isBlank() ) {\n                throw new IllegalStateException( \"@OrderColumn on unowned side: \"\n                    + c.getName() + \".\" + f.getName() );\n            }\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep join and ordering metadata exclusively on the owning side of bidirectional relations","When adding @OrderColumn, search the mapping pair and edit only the side without mappedBy","Order inverse-side iteration in queries (ORDER BY) rather than mapping an index"],"tags":["hibernate","jpa","order-column","many-to-many","mapped-by","annotation-binding"],"backgroundTag":"ordercolumn-mappedby-conflict","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}