There is actually a difference between CustomSqlChange and how modifySql works. They are also both different than how the extension system in 2.0 works. The goal of all three is to allow writers of the changelog to override built-in standard liquibase functionality if they need something more or something different.
CustomSqlChange was created first, and allows people to create a java class that implements the CustomSqlChange interface. They can add a tag to their changeset with a class attribute that lists the class that implements CustomSqlChange and the class is ran like it was any other liquibase change. The way it is actually implemented is a built-in CustomChangeWrapper class that calls out to the CustomSqlChange implementation.
ModifySql was then added for people who don’t want to (or cannot) create a custom class for the functionality they want. Instead, they can use the to define text replacements/changes to the generated SQL. This allows them to add an engine=“INNODB” clause to the end of a create table change, for example.
2.0 added the extension system which lets users take much fuller control of available changes and generated SQL, but custom and modifySql are still there for compatability. modifySql still makes sense as an option since it works well for non-java devs, but CustomSqlChange has been superseded by the extension system. You can use the modifyColumn extension as an example with clj-liquibase (http://liquibase.jira.com/wiki/display/CONTRIB/ModifyColumn+Change)
From an DatabaseChangeLog object population standpoint, you should be able to call ChangeFactory.getInstance().create(tagname) to get an instance of the Change class, whether it is a built-in class or an extension.
modifySql tags are converted to SqlVistor instances using SqlVisitorFactory.getInstance().create(tagName) and then added to the Change object by change.addSqlVisitor(visitor)