eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Overview

Hibernate simplifies data handling between SQL and JDBC by mapping the Object-Oriented model in Java with the Relational model in Databases. Although mapping of basic Java classes is in-built in Hibernate, mapping of custom types is often complex.

In this tutorial, we’ll see how Hibernate allows us to extend the basic type mapping to custom Java classes. In addition to that, we’ll also see some common examples of custom types and implement them using Hibernate’s type mapping mechanism.

2. Hibernate Mapping Types

Hibernate uses mapping types for converting Java objects into SQL queries for storing data. Similarly, it uses mapping types for converting SQL ResultSet into Java objects while retrieving data.

Generally, Hibernate categorizes the types into Entity Types and Value Types. Specifically, Entity types are used to map domain-specific Java entities and hence, exist independently of other types in the application. In contrast, Value Types are used to map data objects instead and are almost always owned by the Entities.

In this tutorial, we will focus on the mapping of Value types which are further classified into:

  • Basic Types – Mapping for basic Java types
  • Embeddable – Mapping for composite java types/POJO’s
  • Collections – Mapping for a collection of basic and composite java type

3. Maven Dependencies

To create our custom Hibernate types, we’ll need the hibernate-core dependency:

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>6.5.2.Final</version>
</dependency>

4. Custom Types in Hibernate

We can use Hibernate basic mapping types for most user domains. However, there are many use cases, where we need to implement a custom type.

Hibernate makes it relatively easier to implement custom types. There are three approaches to implementing a custom type in Hibernate. Let’s discuss each of them in detail.

4.1. Implementing BasicType

We can create a custom basic type by implementing Hibernate’s BasicType or one of its specific implementations, AbstractSingleColumnStandardBasicType.

Before we implement our first custom type, let’s see a common use case for implementing a basic type. Suppose we have to work with a legacy database, that stores dates as VARCHAR. Normally, Hibernate would map this to String Java type. Thereby, making date validation harder for application developers. 

So let’s implement our LocalDateString type, which stores LocalDate Java type as VARCHAR:

public class LocalDateStringType extends AbstractSingleColumnStandardBasicType<LocalDate> {

    public static final LocalDateStringType INSTANCE = new LocalDateStringType();

    public LocalDateStringType() {
        super(VarcharJdbcType.INSTANCE, LocalDateStringJavaDescriptor.INSTANCE);
    }

    @Override
    public String getName() {
        return "LocalDateString";
    }

    public LocalDate stringToObject(String xml) {
        return fromString(xml);
    }

    public String objectToSQLString(LocalDate value, Dialect dialect) {
        return '\'' + LocalDateStringJavaDescriptor.INSTANCE.toString(value) + '\'';
    }

}

The most important thing in this code is the constructor parameters. First, is an instance of SqlTypeDescriptor, which is Hibernate’s SQL type representation, which is VARCHAR for our example. And, the second argument is an instance of JavaTypeDescriptor which represents Java type.

Now, we can implement a LocalDateStringJavaDescriptor for storing and retrieving LocalDate as VARCHAR:

public class LocalDateStringJavaDescriptor extends AbstractTypeDescriptor<LocalDate> {

    public static final LocalDateStringJavaDescriptor INSTANCE = new LocalDateStringJavaDescriptor();

    public LocalDateStringJavaDescriptor() {
        super(LocalDate.class, ImmutableMutabilityPlan.INSTANCE);
    }
	
    // other methods
}

Next, we need to override wrap and unwrap methods for converting the Java type into SQL. Let’s start with the unwrap:

@Override
public <X> X unwrap(LocalDate value, Class<X> type, WrapperOptions options) {

    if (value == null)
        return null;

    if (String.class.isAssignableFrom(type))
        return (X) DateTimeFormatter.ISO_LOCAL_DATE.format(value);

    throw unknownUnwrap(type);
}

Next, the wrap method:

@Override
public <X> LocalDate wrap(X value, WrapperOptions options) {
    if (value == null)
        return null;

    if(String.class.isInstance(value))
        return LocalDate.from(DateTimeFormatter.ISO_LOCAL_DATE.parse((CharSequence) value));

    throw unknownWrap(value.getClass());
}

unwrap() is called during PreparedStatement binding to convert LocalDate to a String type, which is mapped to VARCHAR. Likewise, wrap() is called during ResultSet retrieval to convert String to a Java LocalDate.

Finally, we can use our custom type in our Entity class:

@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

    @Column
    @Type(value = UserTypeLegacyBridge.class, 
        parameters = @Parameter(name = UserTypeLegacyBridge.TYPE_NAME_PARAM_KEY, 
        value = "LocalDateString")
    
    private LocalDate dateOfJoining;

    // other fields and methods
}

Later, we’ll see how we can register this type in Hibernate. And as a result, refer to this type using the registration key instead of the fully qualified class name.

4.2. Implementing UserType

With the variety of basic types in Hibernate, it is very rare that we need to implement a custom basic type. In contrast, a more typical use case is to map a complex Java domain object to the database. Such domain objects are generally stored in multiple database columns. UserType interface offers the possibility to map a column from database to a multiple fields in a Java object.

So let’s implement a complex Salary object by implementing UserType:

public class SalaryType implements UserType<Salary> {
    
    @Override
    public int sqlType() {
        return Types.VARCHAR;
    }

    @Override
    public Class returnedClass() {
        return Salary.class;
    }

    // other methods
}

Here, the overridden sqlType method returns the SQL types of field. Similarly, the returnedClass method returns our Salary Java type.

The only thing left to do is to implement the methods to convert between Java type and SQL type, as we did for our BasicType.

First, the nullSafeGet method:

@Override
public Salary nullSafeGet(ResultSet rs, int position, 
    SharedSessionContractImplementor session, Object owner) throws SQLException {
    
    Salary salary = new Salary();

    String salaryValue = rs.getString(position);

    salary.setAmount(Long.parseLong(salaryValue.split(" ")[1]));

    salary.setCurrency(salaryValue.split(" ")[0]);

    return salary;
}

The nullSafeGet method is called when we retrieve information from database and we map that information into the Java object. As we already mentioned the UserType interface offers the possibility to map a single column from database to a multiple fields in Java. In the nullSafeGet method we parse the result and set on the Java object.

Next, the nullSafeSet method:

@Override
public void nullSafeSet(PreparedStatement st, Salary value, int index, 
    SharedSessionContractImplementor session) throws SQLException {
    
    if (Objects.isNull(value))
        st.setNull(index, Types.VARCHAR);
    else {
        Long salaryValue = SalaryCurrencyConvertor.convert(value.getAmount(), value.getCurrency(), 
            localCurrency);
        st.setString(index, value.getCurrency() + " " + salaryValue);
    }
}

In the nullSafeSet method is used when we store the information into database. Doing this we set that information on a single column from database.

Finally, we can declare our custom SalaryType in our OfficeEmployee entity class:

@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

     @Type(value = com.baeldung.hibernate.customtypes.SalaryType.class, 
         parameters = {@Parameter(name = "currency", value = "USD")})
     
     private Salary salary;
	
    // other fields and methods
}

4.3. Implementing CompositeUserType

Implementing UserType works well for straightforward types. However, mapping complex Java types (with Collections and Cascaded composite types) need more sophistication. Hibernate allows us to map such types by implementing the CompositeUserType interface. This interface allow us to map multiple database columns to multiple fields in Java object.

So, let’s see this in action by implementing an AddressType for the OfficeEmployee entity we used earlier:

public class AddressType implements CompositeUserType<Address> {

@Override
public Object getPropertyValue(Address component, int property) throws HibernateException {

    switch (property) {
        case 0:
            return component.getAddressLine1();
        case 1:
            return component.getAddressLine2();
        case 2:
            return component.getCity();
        case 3:
            return component.getCountry();
        case 4:
            return component.getZipCode();
        default:
            throw new IllegalArgumentException(property + 
                " is an invalid property index for class type " + component.getClass()
                .getName());
        }
    }

    @Override
    public Address instantiate(ValueAccess values, SessionFactoryImplementor sessionFactory) {
        return new Address(values.getValue(0, String.class), 
            values.getValue(1,String.class), 
            values.getValue(2, String.class), 
            values.getValue(3, String.class), 
            values.getValue(4,Integer.class));
    }

    @Override
    public Class<?> embeddable() {
        return Address.class;
    }

    @Override
    public Class<Address> returnedClass() {
        return Address.class;
    }

    //other methods
}

Contrary to UserTypes, which maps to a single database column, CompositeType maps property names of our Address class.

Additionally, we also need to implement getPropertyValue and instantiate methods for mapping PreparedStatement and ResultSet indexes to type property. As an example, consider getPropertyValue for our AddressType:

@Override
public Object getPropertyValue(Object component, int property) throws HibernateException {

    Address empAdd = (Address) component;

    switch (property) {
    case 0:
        return empAdd.getAddressLine1();
    case 1:
        return empAdd.getAddressLine2();
    case 2:
        return empAdd.getCity();
    case 3:
        return empAdd.getCountry();
    case 4:
        return Integer.valueOf(empAdd.getZipCode());
    }

    throw new IllegalArgumentException(property + " is an invalid property index for class type "
      + component.getClass().getName());
}

Please note that CompositeType‘s are generally implemented as an alternative mapping mechanism to Embeddable types.

4.4. Type Parameterization

Besides creating custom types, Hibernate also allows us to alter the behavior of types based on parameters.

For instance, suppose that we need to store the Salary of our OfficeEmployee. More importantly, the application must convert the salary amount into geographical local currency amount.

So, let’s implement our parameterized SalaryType which accepts currency as a parameter:

public class SalaryType implements UserType<Salary>, DynamicParameterizedType {

    private String localCurrency;
	
    @Override
    public void setParameterValues(Properties parameters) {
        this.localCurrency = parameters.getProperty("currency");
    }
	
    // other method implementations from CompositeUserType
}

Please note that we have skipped the CompositeUserType methods from our example to focus on parameterization. Here, we simply implemented Hibernate’s DynamicParameterizedType, and overridden the setParameterValues() method. Now, the SalaryType accepts a currency parameter and will convert any amount before storing it.

We’ll pass the currency as a parameter while declaring the Salary:

@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

    @Type(value = com.baeldung.hibernate.customtypes.SalaryType, 
        parameters = { @Parameter(name = "currency", value = "USD") })
    private Salary salary;

    // other fields and methods
}

5. Basic Type Registry

Hibernate maintains the mapping of all in-built basic types in the BasicTypeRegistry. Thus, eliminating the need to annotate mapping information for such types.

Additionally, Hibernate allows us to register custom types, just like basic types, in the BasicTypeRegistry. Normally, applications would register custom types while bootstrapping the SessionFactory. Let’s understand this by registering the LocalDateString type we implemented earlier:

private SessionFactory sessionFactory() {
    ServiceRegistry serviceRegistry = StandardServiceRegistryBuilder()
      .applySettings(getProperties()).build();

    MetadataSources metadataSources = new MetadataSources(serviceRegistry);
    Metadata metadata = metadataSources
      .addAnnotatedClass(OfficeEmployee.class)
      .getMetadataBuilder()
      .applyBasicType(LocalDateStringType.INSTANCE)
      .build();
    
    return metadata.buildSessionFactory()
}

private static Properties getProperties() {
    // return hibernate properties
}

6. Conclusion

In this tutorial, we discussed multiple approaches for defining a custom type in Hibernate. Additionally, we implemented a few custom types for our entity class based on some common use cases where a new custom type can come in handy.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)