hibernate/hibernate-orm · error · AnnotationException

A field marked '@Generated(writable=true)' may not specify e

Error message

A field marked '@Generated(writable=true)' may not specify explicit 'sql'

What it means

@Generated supports two modes: read-only generated values (writable=false, the default) which may override the generated SQL fragment via sql, and writable values (writable=true) which the application may also assign and which must not carry an explicit sql. The GeneratedGeneration constructor rejects the combination @Generated(writable=true, sql="...") at bootstrap with this AnnotationException.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/generator/internal/GeneratedGeneration.java:47

public class GeneratedGeneration implements OnExecutionGenerator {

	private final EnumSet<EventType> eventTypes;
	private final boolean writable;
	private final String[] sql;
	private Class<?> generatedType;

	public GeneratedGeneration(EnumSet<EventType> eventTypes) {
		this.eventTypes = eventTypes;
		writable = false;
		sql = null;
	}

	public GeneratedGeneration(Generated annotation) {
		eventTypes = fromArray( annotation.event() );
		sql = isEmpty( annotation.sql() ) ? null : new String[] { annotation.sql() };
		writable = annotation.writable();
		if ( sql != null && writable ) {
			throw new AnnotationException( "A field marked '@Generated(writable=true)' may not specify explicit 'sql'" );
		}
	}

	public GeneratedGeneration(Generated annotation, GeneratorCreationContext context) {
		this( annotation );
		generatedType = context.getType().getReturnedClass();
	}

	@Override
	public EnumSet<EventType> getEventTypes() {
		return eventTypes;
	}

	@Override
	public Class<?> getGeneratedType() {
		return generatedType;
	}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Remove sql and keep writable=true — the value is produced by the database (trigger/default) and the app may still write the field
  2. If you need the custom SQL fragment, set writable = false (default) and keep sql
  3. If the goal is DDL-level generation (GENERATED ALWAYS AS / identity), use @GeneratedColumn instead of @Generated(sql=...)
  4. If you need both app-writability and DB-side SQL, move the SQL into a trigger and let @Generated(writable=true) read back the result

Example fix

// before — rejected combination
@Generated(event = EventType.ALWAYS, writable = true, sql = "current_timestamp")
Instant updated;

// after — writable value generated by the database; no explicit sql
@Generated(event = EventType.ALWAYS, writable = true)
Instant updated;
Defensive patterns

Strategy: validation

Validate before calling

// Fail fast on @Generated(writable=true, sql=...), before booting
static void checkGeneratedAnnotations(Class<?>... entities) {
    for (Class<?> entity : entities) {
        for (Field f : entity.getDeclaredFields()) {
            Generated g = f.getAnnotation(Generated.class);
            if (g != null && g.writable() && !g.sql().isEmpty()) {
                throw new IllegalStateException(entity.getSimpleName() + "." + f.getName()
                        + ": @Generated(writable=true) must not specify sql");
            }
        }
    }
}

Prevention

When it happens

Trigger: Writing @Generated(event = ..., writable = true, sql = "...") on any attribute — the constructor throws as soon as the annotation is processed during mapping.

Common situations: Copy-pasting examples that mix @GeneratedColumn (DDL-level generation) with @Generated(sql=...); wanting an app-writable trigger-generated column while also specifying the trigger SQL; misunderstanding the writable attribute introduced in Hibernate 6.x as 'writable by the SQL fragment'.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/fc6241015107c49c. Report an issue: GitHub.