Hibernate Search still guessing after being told which bridge

I have an abstract ancestor class for the comment table which has a trinary key. The key is in the CommentId class which is marked as @Embeddable. The three fields of the key are a long, enum, and a date. Each field of CommentId has its own @Field annotation explicitly referencing the LongBridge, EnumBridge, and DateBridge classes. Then in the Comment class, the getId() method is marked as @EmbeddedId. In the descendants the getId() method (overridden from the ancestor) is further annotated with @FieldBridge to the custom class I wrote to handle all three. (Because apparently the Search related annotations cannot be put on an abstract class.)

My custom CommentIdFieldBridge implements TwoWayFieldBridge class was built per various examples (not sure if it works or not).

No matter what I do, I still get this error during deployment:

Caused by: org.hibernate.search.exception.SearchException: HSEARCH000135: Unable to guess FieldBridge for id in gov.texas.dps.dld.dls.db.domain.dls.CommentId
	at org.hibernate.search.bridge.impl.BridgeFactory.buildFieldBridge(BridgeFactory.java:283)
	at org.hibernate.search.bridge.impl.BridgeFactory.buildFieldBridge(BridgeFactory.java:185)
	at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.createIdPropertyMetadata(AnnotationMetadataProvider.java:359)
	at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.checkDocumentId(AnnotationMetadataProvider.java:258)
	at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.initializeMemberLevelAnnotations(AnnotationMetadataProvider.java:1062)
	at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.initializeClass(AnnotationMetadataProvider.java:599)
	at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.doGetTypeMetadataFor(AnnotationMetadataProvider.java:192)
	at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.getTypeMetadataFor(AnnotationMetadataProvider.java:181)
	at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.getTypeMetadataFor(AnnotationMetadataProvider.java:165)
	at org.hibernate.search.spi.SearchIntegratorBuilder.initDocumentBuilders(SearchIntegratorBuilder.java:445)

Because apparently the Search related annotations cannot be put on an abstract class

They can.

No matter what I do, I still get this error during deployment

Make sure to put the @FieldBridge annotation in the exact same place as your @EmbeddedId annotation: getter or field. Do not put one annotation on the getter and the other on the field.

If that doesn’t solve your problem, please give us your exact code, including everything related to IDs, with all the classes involved.

Thanks for answering so fast. Well, I put the Indexed and FieldBridge annotations back onto the abstract Comment class and removed them from the descendants. I am back to where I started. The app deploys, no index directories are created and I have 8 of the following messages even though there are 15 descendants.

09:57:11,311 WARN [org.hibernate.search.spi.SearchIntegratorBuilder] (MSC service thread 1-6) HSEARCH000044: Abstract classes cannot be indexed directly. Only concrete subclasses can be indexed. Indexed on 'db.domain.dls.Comment' is superfluous and should be removed.

@DiscriminatorColumn(name = "CMTS_COMMENT_TYP", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorOptions(force = true, insert = false)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Indexed
@Table(schema = "DLNDLF", name = "COMMENT_INFO")
@AttributeOverrides({
	@AttributeOverride(name = "userId", column = @Column(name = "CMTS_USER_ID", 
		insertable = true, nullable = false, updatable = true, length = FIELD_LEN_MAX_USER_ID)),
	@AttributeOverride(name = "userFunction", column = @Column(name = "CMTS_LST_BSN_FUNC", 
		insertable = true, nullable = false, updatable = true, length = FIELD_LEN_MAX_LAST_BUS_FUNC)) 
})
@MappedSuperclass
public abstract class Comment extends BaseDLSDataEntity<CommentId> implements Serializable {
...
	@EmbeddedId
	@FieldBridge(impl = gov.texas.dps.dld.dls.db.search.bridge.CommentIdFieldBridge.class)
	@NotNull(message = "comment.id.required")
	public CommentId getId() {
...
	@Column(name = "CMTS_COMMENTS", insertable = true, nullable = false, updatable = true, length = FIELD_LEN_MAX_COMMENT)
	@Field(analyze = Analyze.YES, index = Index.YES, store = Store.NO)
	@Length(max = FIELD_LEN_MAX_COMMENT, message = "comment.comment.validLength")
	@NotNull(message = "comment.comment.required")
	public String getComment() {
...
@Embeddable
public class CommentId implements Serializable 
	private static final long serialVersionUID = 1L;
	public static final int FIELD_LEN_MAX_COMMENT_TYPE = 2;
	private Long commentId; // decimal(17), not null, related object's id
	private CommentTypeEnum commentTypeEnum; // char(2), not null, see discriminator
	private Timestamp recordDate; // timestamp, not null
...
	@Column(name = "CMTS_COMMENT_ID", insertable = true, nullable = false, updatable = false)
	@Field(analyze = Analyze.NO, index = Index.YES, bridge = FieldBridge(impl = LongBridge.class))
	@NotNull(message = "comment.id.commentId.required")
	public Long getCommentId() {
...
	@Column(name = "CMTS_COMMENT_TYP", insertable = false, nullable = false, updatable = false, length = FIELD_LEN_MAX_COMMENT_TYPE)
	@Field(analyze = Analyze.NO, index = Index.YES, bridge = FieldBridge(impl = EnumBridge.class)) 
	@Type(type = "db.domain.dls.type.CommentTypeEnumType")
	@NotNull(message = "comment.id.commentTypeEnum.required")
	public CommentTypeEnum getCommentTypeEnum() {
...
	@Column(name = "CMTS_COMMENT_DTE", insertable = true, nullable = false, updatable = false)
	@Field(analyze = Analyze.NO, index = Index.YES, bridge = FieldBridge(impl = DateBridge.class)) 
	@DateBridge(resolution = Resolution.MILLISECOND)
	@NotNull(message = "comment.id.recordDate.required")
	public Timestamp getRecordDate() {

in a descendant:

@DiscriminatorValue("AS") // see: CommentTypeEnum.ADMIN_STATUS.getValue())
@Entity
public class AdministrativeStatusComment extends Comment implements Serializable {

Thanks for the answer, but please leave the code as is, do not remove the @ sign, it’s really hard to read.

You have a </> button in the text editor to tell Discourse to escape code and format it correctly. Please use that instead of blockquotes.

Alternatively, you can put your code between two lines containing just ``` (three backticks). It’s just markdown syntax.

Corrected. Sorry about that, new to this editor.

As the warning message states, @Indexed is useless on abstract classes.

What happens if you remove the @Indexed annotation from the abstract class, and move it to the concrete subclasses, without changing the other annotations? Which error do you get then?

Bringing this back up. I moved the @Indexed and @FieldBridge annotations to the concrete class:

	@FieldBridge(impl = a.b.c.db.search.bridge.CommentIdFieldBridge.class)
	@NotNull(message = "administrativeStatusComment.id.required")
	public CommentId getId() {
		return super.getId();
	}

And at run time I still get the following. WHY is Hibernate trying to “Guess” when it has been TOLD what to use?

Caused by: org.jboss.weld.exceptions.DeploymentException: HSEARCH000135: Unable to guess FieldBridge for id in gov.texas.dps.dld.dls.db.domain.dls.CommentId
	at org.jboss.weld.core@3.1.0.Final//org.jboss.weld.bootstrap.events.AbstractDeploymentContainerEvent.fire(AbstractDeploymentContainerEvent.java:38)
	at org.jboss.weld.core@3.1.0.Final//org.jboss.weld.bootstrap.events.AfterDeploymentValidationImpl.fire(AfterDeploymentValidationImpl.java:28)
	at org.jboss.weld.core@3.1.0.Final//org.jboss.weld.bootstrap.WeldStartup.validateBeans(WeldStartup.java:505)
	at org.jboss.weld.core@3.1.0.Final//org.jboss.weld.bootstrap.WeldBootstrap.validateBeans(WeldBootstrap.java:93)
	at org.jboss.as.weld@16.0.0.Final//org.jboss.as.weld.WeldStartService.start(WeldStartService.java:98)
	at org.jboss.msc@1.4.5.Final//org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1738)
	at org.jboss.msc@1.4.5.Final//org.jboss.msc.service.ServiceControllerImpl$StartTask.execute(ServiceControllerImpl.java:1700)
	... 6 more
Caused by: org.hibernate.search.exception.SearchException: HSEARCH000135: Unable to guess FieldBridge for id in gov.texas.dps.dld.dls.db.domain.dls.CommentId
	at org.hibernate.search.engine@5.10.5.Final//org.hibernate.search.bridge.impl.BridgeFactory.buildFieldBridge(BridgeFactory.java:283)
	at org.hibernate.search.engine@5.10.5.Final//org.hibernate.search.bridge.impl.BridgeFactory.buildFieldBridge(BridgeFactory.java:185)
	at org.hibernate.search.engine@5.10.5.Final//org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.createIdPropertyMetadata(AnnotationMetadataProvider.java:359)
	at org.hibernate.search.engine@5.10.5.Final//org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.checkDocumentId(AnnotationMetadataProvider.java:258)
	at org.hibernate.search.engine@5.10.5.Final//org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.initializeMemberLevelAnnotations(AnnotationMetadataProvider.java:1062)
	at org.hibernate.search.engine@5.10.5.Final//org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.initializeClass(AnnotationMetadataProvider.java:599)
	at org.hibernate.search.engine@5.10.5.Final//org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.doGetTypeMetadataFor(AnnotationMetadataProvider.java:192)
	at org.hibernate.search.engine@5.10.5.Final//org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.getTypeMetadataFor(AnnotationMetadataProvider.java:181)
	at org.hibernate.search.engine@5.10.5.Final//org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.getTypeMetadataFor(AnnotationMetadataProvider.java:165)
	at org.hibernate.search.engine@5.10.5.Final//org.hibernate.search.spi.SearchIntegratorBuilder.initDocumentBuilders(SearchIntegratorBuilder.java:445)

On a side note, I also noticed:

07:20:47,887 INFO  [org.hibernate.search.store.impl.DirectoryProviderHelper] (MSC service thread 1-1) HSEARCH000041: Index directory not found, creating: './gov.texas.dps.dld.dls.db.domain.dls.CardStatusComment'

Which shouldn’t be “happening” since I have commented out the search related settings in my persistence.xml file:

            <!-- hibernate search 
            <property name="hibernate.search.default.directory_provider"
                value="org.hibernate.search.store.impl.FSDirectoryProvider" />
            <property name="hibernate.search.default.indexBase" value="/indexes/dls" />
            -->

Hopefully that is informational only and not actually doing anything in the file system (I didn’t see a folder created within the Wildfly directory).

FYI: Wildfly 16, Hibernate 5.3.9, Hibernate Search 5.10.5.

Well, probably because it didn’t hear what you said.

Where is your @EmbeddedId annotation? On the same getter, in the same class? On the same getter, in the superclass? On a field instead of the getter?

Hibernate Search will only find the @FieldBridge annotation if it’s on the same property that has the @Id/@EmbeddedId/@DocumentId.

If you need to define the ID in a superclass and the bridge in the child class, put an explicit @DocumentId annotation on getId in the child class, so that Hibernate Search will pull all the metadata from that method declaration instead of the one in the superclass.

Yes, this is annoying and being worked on.

Yes it should, the FSDirectoryProvider is the default, and the default indexBase is the current workind directory.

The class structures are:

The compound primary key (PK) class:

@Embeddable
public class CommentId implements Serializable {

	private static final long serialVersionUID = 1L;
	public static final int FIELD_LEN_MAX_COMMENT_TYPE = 2;

	private Long commentId; // decimal(17), not null, related object's id
	private CommentTypeEnum commentTypeEnum; // char(2), not null, see discriminator
	private Timestamp recordDate; // timestamp, not null

With just @column annotations in the PK class.

The abstract table class:

@DiscriminatorColumn(name = "CMTS_COMMENT_TYP", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorOptions(force = true, insert = false)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(schema = "DLNDLF", name = "COMMENT_INFO")
@AttributeOverrides({
	@AttributeOverride(name = "userId", column = @Column(name = "CMTS_USER_ID", 
		insertable = true, nullable = false, updatable = true, length = FIELD_LEN_MAX_USER_ID)),
	@AttributeOverride(name = "userFunction", column = @Column(name = "CMTS_LST_BSN_FUNC", 
		insertable = true, nullable = false, updatable = true, length = FIELD_LEN_MAX_LAST_BUS_FUNC)) 
})
@MappedSuperclass
public abstract class Comment extends BaseDLSDataEntity<CommentId> implements Serializable {

	private CommentId id;
	private Integer recordWeek; // integer, not null
	private Timestamp lastUpdate; // timestamp, null
	private String lastUpdateType; // char(2), null
	private String comment; // varchar(2048), not null

	public Comment() {
		super();
		id = new CommentId();
		Calendar c = Calendar.getInstance();
		id.setRecordDate(new Timestamp(c.getTimeInMillis()));
		recordWeek = c.get(Calendar.WEEK_OF_YEAR);
	}

	@EmbeddedId
	@FieldBridge(impl = gov.texas.dps.dld.dls.db.search.bridge.CommentIdFieldBridge.class)
	@NotNull(message = "comment.id.required")
	public CommentId getId() {
		return id;
	}

and the first (of many currently commented out) concrete classes:

@DiscriminatorValue("AS") // see: CommentTypeEnum.ADMIN_STATUS.getValue())
@Entity
@Indexed
public class AdministrativeStatusComment extends Comment implements Serializable {
...
	@DocumentId
	@NotNull(message = "administrativeStatusComment.id.required")
	public CommentId getId() {
		return super.getId();
	}

Same message. Tried both your suggestions.

The code you gave will not work, I can understand that, because there is a @DocumentId annotation, and it is not on the same method as @FieldBridge. You original example showed the @FieldBridge annotation on the subclass, here it’s on the superclass.

What have you tried exactly? Please try this in the superclass:

	@EmbeddedId
	@FieldBridge(impl = gov.texas.dps.dld.dls.db.search.bridge.CommentIdFieldBridge.class)
	@NotNull(message = "comment.id.required")
	public CommentId getId() {
		return id;
	}

and this in the subclass:

	// Do NOT put a @DocumentId
	@NotNull(message = "administrativeStatusComment.id.required")
	public CommentId getId() {
		return super.getId();
	}

Please leave everything else as it was in your code snippet. Especially the @Indexed annotation, which must be on the subclass and is unrelated to your problem.

Did as you asked. Same error:

Caused by: org.hibernate.search.exception.SearchException: HSEARCH000135: Unable to guess FieldBridge for id in gov.texas.dps.dld.dls.db.domain.dls.CommentId

I put everything in the abstract class but @Indexed on the descendant/concerete class.

The error message needs to be complaining about the comment class itself. Is there something about the id class it doesn’t like?

@Embeddable
public class CommentId implements Serializable {

	private static final long serialVersionUID = 1L;
	public static final int FIELD_LEN_MAX_COMMENT_TYPE = 2;

	private Long commentId; // decimal(17), not null, related object's id
	private CommentTypeEnum commentTypeEnum; // char(2), not null, see discriminator
	private Timestamp recordDate; // timestamp, not null
...
	@Column(name = "CMTS_COMMENT_ID", insertable = true, nullable = false, updatable = false)
	@Field(analyze = Analyze.NO, index = Index.YES, bridge = @FieldBridge(impl = LongBridge.class))
	@NotNull(message = "comment.id.commentId.required")
	public Long getCommentId() {
		return commentId;
	}

	@Column(name = "CMTS_COMMENT_TYP", insertable = false, nullable = false, updatable = false, length = FIELD_LEN_MAX_COMMENT_TYPE)
	@Field(analyze = Analyze.NO, index = Index.YES, bridge = @FieldBridge(impl = EnumBridge.class)) 
	@Type(type = "gov.texas.dps.dld.dls.db.domain.dls.type.CommentTypeEnumType")
	@NotNull(message = "comment.id.commentTypeEnum.required")
	public CommentTypeEnum getCommentTypeEnum() {
		return commentTypeEnum;
	}

	@Column(name = "CMTS_COMMENT_DTE", insertable = true, nullable = false, updatable = false)
	@Field(analyze = Analyze.NO, index = Index.YES)
	@DateBridge(resolution = Resolution.MILLISECOND)
	@NotNull(message = "comment.id.recordDate.required")
	public Timestamp getRecordDate() {
		return recordDate;
	}

I have a theory as to what may be going on. The abstract Comment class:

@MappedSuperclass
public abstract class Comment extends BaseDLSDataEntity<CommentId> implements Serializable {

descends from:

@MappedSuperclass
public class BaseDLSDataEntity<ID extends Serializable> extends Domain<ID> implements Serializable {

which descends from:

@MappedSuperclass
public abstract class Domain<ID extends Serializable> implements Serializable {
	public static final int FIELD_LEN_MAX_WHO = 15;

	private ID id;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "id", insertable = true, nullable = true, updatable = false)
	public ID getId() {
		return id;
	}

Note that the Domain ancestor has a generic member which has the @Id value attached.

Despite the presence of:

	@DocumentId
	@FieldBridge(impl = gov.texas.dps.dld.dls.db.search.bridge.CommentIdFieldBridge.class)
	@Transient
	public CommentId getDocumentId() {
		return super.getId();
	}

In the abstract class, the Search reflection code keeps going back to the generic field Id which is annotated with @Id instead of using the getter marked as @DocumentId.

Why?

YEP, that was it! ARGH! I guess there’s no work around for an ancestral @Id ?

So it seems Hibernate Search tries to process Ids from superclasses before even checking for @DocumentId in subclasses. It’s fixed in Search 6’s Alpha versions, but you will need a solution in Search 5.

I think there is an easy workaround, but it’s a bit dirty. Add the following method to your Domain class:

// Workaround for https://discourse.hibernate.org/t/hibernate-search-still-guessing-after-being-told-which-bridge/2549/
@DocumentId
protected int getDummyDocumentId() {
    throw new UnsupportedOperationException( "This should never be called" );
}

This should make Hibernate Search happy when it encounters getId() while processing the mapping of Domain, and it will just be forgotten about when Search encounters the actual document ID in Comment.

Alternatively you can change Domain.getId to add a dummy bridge that won’t do anything, just so Hibernate Search doesn’t try to find one automatically:

	@FieldBridge(impl = gov.texas.dps.dld.dls.db.search.bridge.DummyIdFieldBridge.class)
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "id", insertable = true, nullable = true, updatable = false)
	public ID getId() {
		return id;
	}

And implement DummyIdFieldBridge just like CommentIdFieldBridge, but throw an UnsupportedOperationException in runtime methods.

On a side note, if your model is new, you might want to consider moving the @Id annotation to the @Entity classes, even if getId() is defined in the mapped superclass: last time I checked, putting the @Id annotation on the mapped superclass resulted in the ID generation strategy being shared among all entity types, i.e. the same sequence (or, in your case, the same ID table) was being used for all tables. It might not be what you want.

That actually did the trick. I also ended up having to move these guys:

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "id", insertable = true, nullable = false, updatable = false)
	public Long getId() {
		return super.getId();
	}

Into the descendant classes, though I left the generic ID field in Domain. This way, in the descendants that use identity or @EmbeddedId classes (such as Comment) I can make the annotations work as desired and expected.

Thanks so much for the insight and guidance! Now I have to wait in WF 17 and Hibernate X and Hibernate Search Y to adjust this again?

Glad it worked :slight_smile:

WildFly has quite strict backward compatibility policies, so this should work fine in the next versions of WildFly too.

There’s no ETA for Hibernate Search 6 yet, so all I can tell is it’s not in WildFly 17 and probably won’t be in 18 either. Rest assured we’re working hard to make it happen as soon as possible, though.