Type Coercion is the conversion of one type of object to a another object of a different type with similar content. Tapestry frequently must coerce objects from one type to another. A common example is the coercion of string "5" into an integer 5 or a double 5.0.
Although type coercions happen more inside tapestry-core (including coercions of ), they may also occur inside tapestry-ioc, such as when injecting a value, rather than a service, into a builder method.
Like everything else in Tapestry, type coercions are extensible. At the root is the TypeCoercer service. Its configuration consists of CoercionTuples until 5.6.x and a mapped configuration of (CoercionTuple.Key, CoercionTuple) from Tapestry 5.7.0 on. Each tuple defines how to coerce from one type to another. The initial set of coercions is focused primarily on coercions between different numeric types:
Default Type Coercions
There are a few special coercions related to
List wraps a lone object as a singleton list, we then need
List to ensure that
null (rather than a singleton list whose lone element is a
Type Coercion Interpolation
Tapestry will try to interpolate necessary coercions. For example, say it is necessary to coerce a
StringBuffer to an
Integer; the TypeCoercer service will chain together a series of coercions:
Object --> String is always available, this might lead to an unexpected interpolation between incompatible types due to multiple intermediate coercions. This can be easily prevented by providing an explicit CoercionTuple between the desired types.
Coercing from null
null is special; it is not a spanning search as with the other types. Either there is a specific coercion from
null to the desired type, or no coercion takes places (and the coerced value is
The only built-in
null coercion is from
boolean (which is always false).
List of Coercions
As of Tapestry version 5.7, the following coercions are available:
Contributing New Coercions
TypeCoercer is extensible; you may add new coercions as desired. For example, let's say you have a
Money type that represents an amount of some currency, and you want to be able to convert from
Money. Further, let's assume that
Money has a constructor that accepts a
BigDecimal as its parameter. We'll use a little Tapestry IOC configuration jujitsu to inform the TypeCoercer about this coercion.
Further, since TypeCoercer knows how to convert
BigDecimal, or even
BigDecimal, all of those coercions would work as well.
When creating a coercion from
Void.class as the source type. For example, the built-in coercion from
Boolean is implemented as:
java.time (JSR310) Type Coercers
With Java 8, the Java Time API (JSR310) got added to the JDK, providing a new, more straightforward way of dealing with date and time. The multitude of different types created a need for additional TypeCoercers. But due to the way the Java Time API was implemented, you must be aware of some edge cases and intricacies.
Even though the Java Time API partially supports nanosecond precision, it's not guaranteed. Actually, the OS / the JVM implementation is responsible for providing the current time with
java.time.Clock. Most of the new types even don't have convenience-methods for a nanosecond-based representation.
All date(time)-based types use
java.time.Instant for coercion, which itself is coerced with milliseconds precision. This provides compatibility with
java.util.Date#getTime(), which is also milliseconds-based.
The 2 types not using milliseconds are not subclasses of or convertible to
java.time.LocalTime: Uses nanoseconds internally, and no convenience method for milliseconds exists.
java.time.Duration: Uses nanoseconds and seconds internally. Even though a convenience method for milliseconds exists, the type isn't dependent on the OS/JVM precision. So the highest possible precision is used.
The Java Time API supports timezone-less types, e.g.
However, coercing these types into a `java.time.Instant` requires a timezone, because
java.time.Instant uses the unix timestamp 0 as reference.
To still use automatic type coercion, the Local types will be seen as being in
java.time.LocalDate we use the local time of 00:00 for it's coercion to
Invalid Coercion Paths
Due to the powerful feature of finding compatible TypeCoercer paths to convert types with no explicit TypeCoercer, some additional TypeCoercers were added to ensure the correct conversion between types.
You should only try to coerce between the provided types and
java.time.Instant as intermediate. Any other coercion path will require you to contribute a matching TypeCoercer yourself.