Skip to content

Instantly share code, notes, and snippets.

@shawnz
Last active October 23, 2025 10:19
Show Gist options
  • Select an option

  • Save shawnz/8f7308741e2ff6270cc8878ac69b4f6b to your computer and use it in GitHub Desktop.

Select an option

Save shawnz/8f7308741e2ff6270cc8878ac69b4f6b to your computer and use it in GitHub Desktop.
Hibernate schema validator that outputs warning messages instead of throwing exceptions. Tested with Hibernate 5.3.9
spring.jpa.hibernate.ddl-auto = validate
spring.jpa.properties.hibernate.schema_management_tool = org.shawnz.WarnValidatorSchemaManagementTool
package org.shawnz;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.Sequence;
import org.hibernate.dialect.Dialect;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Table;
import org.hibernate.tool.schema.extract.spi.ColumnInformation;
import org.hibernate.tool.schema.extract.spi.SequenceInformation;
import org.hibernate.tool.schema.extract.spi.TableInformation;
import org.hibernate.tool.schema.internal.GroupedSchemaValidatorImpl;
import org.hibernate.tool.schema.internal.HibernateSchemaManagementTool;
import org.hibernate.tool.schema.spi.ExecutionOptions;
import org.hibernate.tool.schema.spi.SchemaFilter;
import org.hibernate.type.descriptor.JdbcTypeNameMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
import java.util.Locale;
public class WarnSchemaValidator extends GroupedSchemaValidatorImpl {
private static Logger log = LoggerFactory.getLogger(WarnSchemaValidator.class);
public WarnSchemaValidator(HibernateSchemaManagementTool tool, SchemaFilter validateFilter) {
super(tool, validateFilter);
}
// from https://github.com/hibernate/hibernate-orm/blob/2bcb1b0a6d8600ba3a60eae6460dcb52bf5628e7/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java#L113
@Override
protected void validateTable(
Table table,
TableInformation tableInformation,
Metadata metadata,
ExecutionOptions options,
Dialect dialect) {
if ( tableInformation == null ) {
log.warn(
String.format(
"Schema-validation: missing table [%s]",
table.getQualifiedTableName().toString()
)
);
// don't validate columns of non-existent table
return;
}
final Iterator selectableItr = table.getColumnIterator();
while ( selectableItr.hasNext() ) {
final Selectable selectable = (Selectable) selectableItr.next();
if ( Column.class.isInstance( selectable ) ) {
final Column column = (Column) selectable;
final ColumnInformation existingColumn = tableInformation.getColumn( Identifier.toIdentifier( column.getQuotedName() ) );
if ( existingColumn == null ) {
log.warn(String.format(
"Schema-validation: missing column [%s] in table [%s]",
column.getName(),
table.getQualifiedTableName()
)
);
// don't validate type of non-existent column
continue;
}
validateColumnType( table, column, existingColumn, metadata, options, dialect );
}
}
}
@Override
protected void validateColumnType(
Table table,
Column column,
ColumnInformation columnInformation,
Metadata metadata,
ExecutionOptions options,
Dialect dialect) {
boolean typesMatch = column.getSqlTypeCode( metadata ) == columnInformation.getTypeCode()
|| column.getSqlType( dialect, metadata ).toLowerCase(Locale.ROOT).startsWith( columnInformation.getTypeName().toLowerCase(Locale.ROOT) );
if ( !typesMatch ) {
log.warn(
String.format(
"Schema-validation: wrong column type encountered in column [%s] in " +
"table [%s]; found [%s (Types#%s)], but expecting [%s (Types#%s)]",
column.getName(),
table.getQualifiedTableName(),
columnInformation.getTypeName().toLowerCase(Locale.ROOT),
JdbcTypeNameMapper.getTypeName( columnInformation.getTypeCode() ),
column.getSqlType().toLowerCase(Locale.ROOT),
JdbcTypeNameMapper.getTypeName( column.getSqlTypeCode( metadata ) )
)
);
}
// this is the old Hibernate check...
//
// but I think a better check involves checks against type code and then the type code family, not
// just the type name.
//
// See org.hibernate.type.descriptor.sql.JdbcTypeFamilyInformation
// todo : this ^^
}
@Override
protected void validateSequence(Sequence sequence, SequenceInformation sequenceInformation) {
if ( sequenceInformation == null ) {
log.warn(
String.format( "Schema-validation: missing sequence [%s]", sequence.getName() )
);
// don't validate properties of non-existent sequence
return;
}
if ( sequenceInformation.getIncrementSize() > 0
&& sequence.getIncrementSize() != sequenceInformation.getIncrementSize() ) {
log.warn(
String.format(
"Schema-validation: sequence [%s] defined inconsistent increment-size; found [%s] but expecting [%s]",
sequence.getName(),
sequenceInformation.getIncrementSize(),
sequence.getIncrementSize()
)
);
}
}
}
package org.shawnz;
import org.hibernate.boot.registry.selector.spi.StrategySelector;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.tool.schema.internal.DefaultSchemaFilterProvider;
import org.hibernate.tool.schema.internal.HibernateSchemaManagementTool;
import org.hibernate.tool.schema.spi.SchemaFilterProvider;
import org.hibernate.tool.schema.spi.SchemaValidator;
import java.util.Map;
public class WarnValidatorSchemaManagementTool extends HibernateSchemaManagementTool {
@Override
public SchemaValidator getSchemaValidator(Map options) {
return new WarnSchemaValidator(this, getSchemaFilterProvider( options ).getValidateFilter());
}
// from https://github.com/hibernate/hibernate-orm/blob/2bcb1b0a6d8600ba3a60eae6460dcb52bf5628e7/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/HibernateSchemaManagementTool.java#L95
private SchemaFilterProvider getSchemaFilterProvider(Map options) {
final Object configuredOption = (options == null)
? null
: options.get( AvailableSettings.HBM2DDL_FILTER_PROVIDER );
return getServiceRegistry().getService( StrategySelector.class ).resolveDefaultableStrategy(
SchemaFilterProvider.class,
configuredOption,
DefaultSchemaFilterProvider.INSTANCE
);
}
}
@hohwille
Copy link

hohwille commented Aug 8, 2025

@shawnz thank you so much for creating this awesome solution.
Did you even consider contributing this to hibernate?
Does this have any kind of license (could you update this gist to have license header or comment something here)?
I am asking if I could copy&paste this as is into a proprietary software and do not want to violate your copyright.
Surely it is just 2 classes so I could more or less rewrite myself from memory with slight changes and my personal style, but I would prefer to simply put a link in the header of these files pointing to this gist and saying that this is e.g. Apache 2.0 or WTFPL before pushing anything related... Thanks in advance.

@shawnz
Copy link
Author

shawnz commented Aug 8, 2025

@hohwille Since I copied some code from hibernate to make this, those parts fall under the hibernate LGPL license. Otherwise, feel free to use for any purpose, commercial or otherwise

@hohwille
Copy link

👍 Thanks @shawnz for the quick and friendly response.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment