Back to blog
Java 13 min read

Hibernate 6 Type Narrowing: Integer to Byte/Short and Exact JDBC Mappings

Why schema validation fails on TINYINT/SMALLINT after upgrading to Hibernate 6, how to pick the Java type, and when to use @JdbcTypeCode.

HibernateJPASpring BootSQL
Hibernate 6 Type Narrowing: Integer to Byte/Short and Exact JDBC Mappings

What changed in Hibernate 6

With Hibernate 5 many teams mapped a TINYINT column to Integer/int, and SMALLINT to Integer as well. Drivers often widened values quietly, schema validation was looser, and production kept running for years.

Hibernate 6 made the Java type ↔ JDBC type pair strict via JavaType / JdbcType / SqlTypes. Canonical defaults are: byte/Byte → TINYINT, short/Short → SMALLINT, int/Integer → INTEGER. If the database column is narrower than the entity field, ddl-auto=validate fails with wrong column type.

Example 1: TINYINT is no longer “almost Integer”

Legacy mapping:

// Hibernate 5 — often worked
@Column(name = "status_code")
private Integer statusCode; // TINYINT column

After Spring Boot 3 / Hibernate 6:

Schema-validation: wrong column type encountered;
found [tinyint (Types#TINYINT)], but expecting [integer (Types#INTEGER)]

Narrow the Java type to match DDL:

@Column(name = "status_code")
private Byte statusCode; // or byte when null is impossible

If the domain API must stay on Integer, keep the field and declare the JDBC type explicitly:

@JdbcTypeCode(SqlTypes.TINYINT)
@Column(name = "status_code")
private Integer statusCode;

// or
@JdbcType(TinyIntJdbcType.class)
private Integer statusCode;

Example 2: SMALLINT → Short

// Before
private Integer countryId; // SMALLINT in PostgreSQL/MySQL

// Better
private Short countryId;

// Or explicit override
@JdbcTypeCode(SqlTypes.SMALLINT)
private Integer countryId;

Type narrowing is not pedantry — it prevents silent truncation and keeps Flyway/Liquibase DDL aligned with entities.

Example 3: enum default shift

From Hibernate 6.1+, ordinal enums default to SMALLINT instead of TINYINT (enums can exceed 255 values). On MySQL/MariaDB/H2, validate often complains:

public enum OrderStatus { NEW, PAID, SHIPPED, CANCELLED }

@Enumerated(EnumType.ORDINAL)
private OrderStatus status;

Migration options:

  • widen the column to SMALLINT;
  • pin the old JDBC type with @JdbcTypeCode(SqlTypes.TINYINT);
  • prefer @Enumerated(EnumType.STRING) for readable evolution.
@Enumerated(EnumType.ORDINAL)
@JdbcTypeCode(SqlTypes.TINYINT)
private OrderStatus status;

@Enumerated(EnumType.STRING)
@Column(length = 32)
private OrderStatus status;

Example 4: boolean — BOOLEAN, BIT, or TINYINT

PostgreSQL maps Boolean to BOOLEAN. MySQL historically used BIT(1) or TINYINT(1). After an upgrade the dialect may expect a different type code than your legacy schema.

@JdbcTypeCode(SqlTypes.TINYINT)
private Boolean active;

@JdbcTypeCode(SqlTypes.BIT)
private Boolean active;

Same rule: either annotate to match the existing column, or migrate DDL — do not guess both at once.

Example 5: UUID and java.time

@JdbcTypeCode(SqlTypes.UUID)
private UUID id;

private Instant createdAt;
private OffsetDateTime updatedAt;

java.util.Date still works, but new code should narrow to Instant/LocalDateTime/OffsetDateTime to avoid timezone ambiguity. For PostgreSQL uuid columns prefer real UUID types over VARCHAR(36).

Example 6: AttributeConverter instead of a magic Integer

@Converter(autoApply = true)
public class PriorityConverter implements AttributeConverter<Priority, Byte> {
  public Byte convertToDatabaseColumn(Priority p) {
    return p == null ? null : p.getCode(); // 1..5
  }
  public Priority convertToEntityAttribute(Byte code) {
    return Priority.fromCode(code);
  }
}

private Priority priority; // TINYINT column, domain value object/enum

You keep the JDBC type honest and lift the domain model at the same time.

Fast diagnosis

  1. Run spring.jpa.hibernate.ddl-auto=validate on staging before production.
  2. Diff Flyway DDL vs entities for TINYINT/SMALLINT/INT/BIGINT.
  3. Read Hibernate 6.x migration guides (enums, UUID, JSON, arrays).
  4. Prefer @JdbcTypeCode / Java-type change / column migration over heavy columnDefinition hacks.

Migration checklist

  • TINYINT → Byte/byte (or @JdbcTypeCode(SqlTypes.TINYINT) on Integer).
  • SMALLINT → Short/short.
  • Ordinal enums → verify SMALLINT vs legacy TINYINT.
  • Boolean → match BIT/BOOLEAN/TINYINT for your database.
  • Time → Instant/OffsetDateTime; UUID → UUID + SqlTypes.UUID.

Takeaway

Hibernate 6 type narrowing is about an honest contract between column and entity field. int used to hide TINYINT; now you need the precise Java type (byte/short), an explicit @JdbcTypeCode, or a DDL migration. Budget that audit into every Spring Boot 2→3 upgrade — one day of column review saves a week of validate failures across environments.

Alexey Prybytkou

Senior Software Developer

© 2026 All rights reserved