Type Conversion
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Apache Struts 7.4.0 (released 2026-10-02) running on Apache Tomcat 11.0.26 and Java 21, the
combination on which every example was built, deployed and requested; the facts were checked on 2026-10-08 and
2026-10-10 against the Core Developer Guide pages on https://struts.apache.org/core-developers/, the 7.4.0 Javadoc, the
source files tagged STRUTS_7_4_0 and the classes inside the 7.4.0 struts2-core jar (javap). HTTP carries text and
nothing else: a form field named pages arrives as the string "12", a date arrives as whatever the user typed, a
checkbox arrives as "on" or not at all. Type conversion is the part of Struts that turns those strings into the int,
BigDecimal, Date, enum, List or domain object that the action property declares, and turns the property back into
a string when a form or a result renders it. Most of the time it is invisible; this page is about the moments when it is
not: which formats are accepted, how the request locale changes them, how to teach Struts a new type, how collections
and maps of beans are filled from indexed parameter names, and what happens, and what the visitor sees, when a value cannot
be converted. The running scenario is the Bookshelf catalogue of this part: a book has a page count, a publication
date, a list price, an ISBN and a shelf code. The related pages of this section are ValueStack, ActionContext & OGNL,
Parameter Injection and @StrutsParameter,
Validation, Localization and i18n and
ModelDriven, Preparable & CRUD.
Where Conversion Happens
Conversion is not an interceptor. It is a service that the OGNL layer calls whenever it assigns a value to a property
whose type differs from the type of the value. The params interceptor reads the request parameters (the interceptor stack page lists where it sits in defaultStack), asks the value stack to
set each one on the action, and the value stack asks the converter to produce a value of the right type. When the
converter cannot, it does not throw to the caller: it records the failure in a map called conversionErrors of the
ActionContext and moves on to the next parameter. A later interceptor, conversionError, converts those records into
field errors, and workflow decides, from the presence of field errors, that the input result must render. The same
machinery runs in the other direction when a tag such as <s:textfield name="listPrice"/> reads the property back: the
value is converted to a string for the page.
pages=abc, isbn=978-0-13-468599-1"] --> params["params interceptor
one setValue per parameter name"] params --> ognl["OGNL on the value stack
setPages(...), setIsbn(...)"] ognl --> lookup{"Converter for this
property or type?"} lookup -->|"property rule in Action-conversion.properties
or Class-conversion.properties"| custom["Custom converter
(StrutsTypeConverter)"] lookup -->|"type rule in struts-conversion.properties"| custom lookup -->|"neither"| basic["Built-in converters
(XWorkBasicConverter, NumberConverter, DateConverter, ...)"] custom -->|"value"| set["property is set"] basic -->|"value"| set custom -->|"exception"| err["conversionErrors map in ActionContext
(property name, rejected text, target type)"] basic -->|"exception"| err err --> cei["conversionError interceptor
addFieldError + original value kept"] cei --> val["validation interceptor
(conversion validator, validate())"] val --> wf["workflow interceptor
field errors present: return input"] wf --> view["input result renders
fielderror, form shows the original text"]
The picture shows the three facts that the rest of the page keeps returning to. First, a conversion failure is data, not
an exception: the action method still runs unless a later interceptor stops it. Second, the failure is only
reported if conversionError (or the conversion validator) is in the stack and the action implements ValidationAware
(every ActionSupport does). Third, the rejected text is kept, so that the form can show abc again instead of 0.
The classes involved in 7.4.0 live in the package org.apache.struts2.conversion (the guide pages still show
com.opensymphony.xwork2 names in places; the package was renamed in 7.0.0, see Where the Books Are Outdated).
| Type | Role |
|---|---|
|
The interface of a converter: |
|
The registry and dispatcher: reads the conversion properties files, finds the converter for a property or a type, records conversion errors |
|
The built-in converter for |
|
The helper base class for converters written by applications: two methods, |
|
The exception a converter throws to say "this text is not a valid value" |
|
Decides the key class, element class, key property and create-if-null behaviour of collections and maps; the default is |
|
The annotations and enums that express the conversion properties in Java |
|
The interceptor |
The Bookshelf Used on This Page
All examples below are classes and mappings of one WAR, g9typeconv.war, deployed under the context path /g9typeconv
on Tomcat 11.0.26. It is the Bookshelf of the page Struts Getting Started (the Struts BOM
org.apache.struts:struts2-bom:7.4.0 imported in dependencyManagement, struts2-core, the Servlet and Pages APIs with
scope provided, StrutsPrepareAndExecuteFilter mapped to /*) with a few actions added for the experiments. Every
action URL uses the default extension .action, every request in the listings was sent with curl using POST and
--data-urlencode (the brackets and parentheses of indexed names are not legal in a raw request target on Tomcat 11, which
answers 400 Invalid character found in the request target if they are sent unencoded in a GET URL), and the actions
return the plain text of report() through a small JSP, so the listings show exactly what the action received. Every
setter that takes a request parameter carries @StrutsParameter, which Struts 7 requires before a property can be set
from the request (the page Parameter Injection and @StrutsParameter covers that annotation; for this page it is a prerequisite
that explains the annotations visible in the listings). The package is org.example.bookshelf; imports are left out.
| Class | Mapping | Concept |
|---|---|---|
|
|
Fifteen properties of built-in types: |
|
|
An |
|
|
A form with |
|
|
A |
|
|
Null properties of a bean type and of an interface type |
|
|
|
|
|
The same two properties behind a stack that excludes |
Built-in Type Conversion
The Core Developer Guide lists what the framework converts without any configuration: String, boolean and Boolean,
char and Character, the numeric primitives and their wrappers, dates (it says "the SHORT format for the Locale
associated with the current request"), arrays, collections, enumerations, and BigDecimal and BigInteger. The
list is correct as far as it goes; the sections below show what the converters actually accept, because the details decide
whether a form works for a given visitor.
The test action has one property per family. Only the getter and the annotated setter of three of them are shown; the other twelve follow the same pattern.
public class BasicAction extends ActionSupport {
private int pages;
private Integer copies;
private BigDecimal listPrice;
private Format format; // enum Format { PAPERBACK, HARDCOVER, EBOOK }
private Date published;
private LocalDate added;
private String[] tags;
private int[] ratings;
private List<String> keywords;
// ... id, available, featured, price, serial, grade ...
@Override
public String execute() { return SUCCESS; }
public int getPages() { return pages; }
@StrutsParameter public void setPages(int v) { pages = v; }
public Format getFormat() { return format; }
@StrutsParameter public void setFormat(Format v) { format = v; }
public Date getPublished() { return published; }
@StrutsParameter public void setPublished(Date v) { published = v; }
}
One request sets every property:
$ curl -sg -X POST http://localhost:18080/g9typeconv/basic.action \
--data-urlencode pages=12 --data-urlencode copies=3 --data-urlencode id=9000000000 \
--data-urlencode available=true --data-urlencode price=19.95 --data-urlencode listPrice=1234.50 \
--data-urlencode serial=123456789012345678901234567890 --data-urlencode grade=ABC \
--data-urlencode format=HARDCOVER --data-urlencode published=2026-10-02 --data-urlencode added=2026-10-02 \
--data-urlencode tags=a --data-urlencode tags=b --data-urlencode ratings=1 --data-urlencode ratings=5 \
--data-urlencode keywords=x,y --data-urlencode keywords=z
pages = 12 (Integer)
copies = 3 (Integer)
id = 9000000000 (Long)
available = true (Boolean)
featured = null (null)
price = 19.95 (Double)
listPrice = 1234.50 (BigDecimal)
serial = 123456789012345678901234567890 (BigInteger)
grade = A (Character)
format = HARDCOVER (Format)
published = Fri Oct 02 00:00:00 CEST 2026 (Date)
added = 2026-10-02 (LocalDate)
tags = [a, b] (ArrayList)
ratings = [1, 5] (String)
keywords = [x,y, z] (XWorkList)
fieldErrors = {}
Reading the output line by line shows the rules:
-
A single value goes to a scalar; several values with the same name go to an array or a collection (
tags,ratings,keywords), and each element is converted individually. -
A
Stringis never split:keywords=x,yis one element,x,y. Splitting on commas is the job of the page, not of the framework. -
chartakes the first character of the string (ABCbecomesA), and an empty string leaves the default. -
Numbers keep their precision:
BigIntegerandBigDecimalhave no 64-bit limit, andlistPrice=1234.50keeps the trailing zero. -
An enum is matched by constant name and is case sensitive:
format=HARDCOVERworks,format=hardcoveris a conversion error (observed). -
The list for
keywordsis anXWorkList, a frameworkArrayListsubclass that supports the auto-growth of indexed names described in Collections and Maps. -
A
Dateis parsed with the formats listed in Dates, and aLocalDatewith the ISO form only.
Booleans, Checkboxes and Blank Values
Boolean conversion is Boolean.valueOf of the string: only the text true, in any letter case, is true (observed with
Struts 7.4.0, where the converter’s doConvertToBoolean does exactly that). Everything else, including the on that
browsers send for a checkbox without a value attribute, is false and is not an error:
available=on -> available = false (Boolean) fieldErrors = {}
featured=yes -> featured = false (Boolean)
featured=1 -> featured = false (Boolean)
available=TRUE -> available = true (Boolean)
available=true&available=false -> available = true (the first value of a repeated name wins for a scalar)
featured= -> featured = false (Boolean) (an empty string is not null for a Boolean)
The consequence for forms is the classic checkbox problem: an unchecked checkbox sends nothing, so the property is never
touched and keeps its previous (possibly true) value. The checkbox interceptor of defaultStack handles it: a hidden
companion field named __checkbox_featured sets the property to false when the checkbox itself is absent. With the
companion field present and the checkbox absent, the observed result is featured = false; with both present and set to
true, featured = true. The <s:checkbox> tag renders the companion field for you; the tags page of this section
covers it.
Blank values need a separate rule, because a browser submits an empty text field as name=:
| Target | Result of name= |
|---|---|
|
No error; the property keeps its default ( |
|
|
|
|
|
|
|
No error; the property stays at the default character |
|
The empty string |
|
|
enum, |
|
The guide’s "Null and Blank Values" section says that "a blank string also cannot be set on a BigDecimal or BigInteger".
That is not the behaviour of 7.4.0 (see Inconsistencies in the Official Documentation). The
other half of the advice stands: a primitive cannot be null, so an action that must tell "not entered" from "zero"
declares the wrapper type and uses validation to demand a value.
Numbers and the Request Locale
NumberConverter converts with java.text.NumberFormat for the locale of the request: the locale chosen by the i18n
interceptor (from request_locale, the session or the browser’s Accept-Language). That makes the decimal and grouping
separators locale dependent, in two different ways for two kinds of target:
# JVM and request locale en-US
copies=1,234 -> copies = 1234 (Integer) grouping separator accepted for a wrapper
pages=1,234 -> fieldErrors = {pages=[...]} the primitive int does not accept it
copies=12.7 -> fieldErrors = {copies=[...]} Integer: "Unparseable number ... at position 2"
copies= 5 -> fieldErrors = {copies=[...]} leading space
copies=99999999999 -> fieldErrors = {copies=[...]} overflow is an error, not a wrap-around
price=1,5 -> price = 15.0 (Double) "," is the grouping separator in en-US: silently ignored
listPrice=1,5 -> listPrice = 15 (BigDecimal)
price=1e3 -> fieldErrors = {price=[...]} exponent notation is rejected
id=7L -> fieldErrors = {id=[...]}
# the same form with Accept-Language: de-DE
price=1,5 -> price = 1.5 (Double)
listPrice=1.234,5 -> listPrice = 1234.5 (BigDecimal)
Source: Struts Javadoc — NumberConverter
Three things to remember. A decimal comma typed by a visitor whose locale is en-US is not an error: it is dropped as a
grouping separator and 1,5 becomes fifteen. The primitive int and long go through the plain DefaultTypeConverter
(no grouping), whereas the wrapper types and BigDecimal use NumberFormat. And the locale is the one the framework
resolved, which is why a form and its action must agree on how the locale is chosen (the page Localization and i18n
covers the i18n interceptor). For prices the safest design is a BigDecimal property, an explicit NumberFormat-aware
validation rule, and the same locale for rendering and parsing.
Dates
The Core Developer Guide says dates use "the SHORT format for the Locale associated with the current request". In 7.4.0
DateConverter is more generous: for a java.util.Date property it tries a list of DateFormat instances in order and
uses the first that parses. If a global format is defined through the struts.date.format text key it goes first; then
the formats are:
| Order | Format |
|---|---|
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
Source: Struts Javadoc — DateConverter
The results on published (a java.util.Date, JVM locale en-US, time zone Central Europe):
published=2026-10-02 -> Fri Oct 02 00:00:00 CEST 2026 format 8
published=2026-10-02T10:15:30 -> Fri Oct 02 10:15:30 CEST 2026 format 4
published=10/02/2026 -> Fri Oct 02 00:00:00 CEST 2026 SHORT, month first in en-US
published=Oct 2, 2026 -> Fri Oct 02 00:00:00 CEST 2026 MEDIUM
published=2026-10-02 10:15:30 -> Fri Oct 02 00:00:00 CEST 2026 time lost: format 8 stops reading after the date
published=02.10.2026 -> fieldErrors = {published=[...]} not an en-US format
published=2026/10/02 -> fieldErrors = {published=[...]}
published=Friday, October 2, 2026 -> fieldErrors = {published=[...]} FULL is not in the list
published=2026-13-45 -> fieldErrors = {published=[...]} non-lenient parsing for the final fallback
Two behaviours deserve a warning. The year-first yyyy-MM-dd format reads only the leading part of the text, so
2026-10-02 10:15:30 (with a space) silently becomes midnight, while the T form keeps its time. And the result of a
numeric short date depends on the locale of the request, as the same value 02/10/2026 is 2 October for a visitor on
en-GB and 10 February for en-US (the SHORT pattern decides which field is the month). For forms that matter, define one
format and use it for input and output: a text key struts.date.format in the global resource bundle puts that format
first, or a custom converter (see below) owns the format completely.
The java.time types are handled separately: LocalDateTime, LocalDate and LocalTime are parsed with
ISO_LOCAL_DATE_TIME, ISO_LOCAL_DATE and ISO_LOCAL_TIME (preceded by the global format when one is defined), and
OffsetDateTime with ISO_OFFSET_DATE_TIME. That is why added=2026-10-02 works and added=02/10/2026 fails with a
conversion error (observed). The guide does not mention java.time at all.
Arrays, Collections and String Properties
For an array or a collection property the converter converts each submitted value to the element type and builds the
container: an array of the property’s type, a HashSet for Set, a TreeSet for SortedSet and an XWorkList (a
framework ArrayList subclass) for List and Collection (read from CollectionConverter in the 7.4.0 source). When the element type cannot be determined the guide says elements are treated as String. The
Set<String> genres of CollectionAction collapses duplicates:
genres=a genres=b genres=a -> genres = [a, b]
ratings=1 ratings=x -> ratings stays null; fieldErrors = {ratings=[...]} (one bad element rejects the array)
A property of type String receives the raw parameter; an Object or Number property that already holds a compatible
value is left alone. The framework also converts to Class (by Class.forName) and to Calendar; both are rarely
useful for request input and Class conversion from request text is a reason to keep the allowlist of the OGNL layer
switched on (the page Security Hardening covers it).
Parameter Names, OGNL and Null Property Handling
The guide gives the central rule in one sentence: there is no need to capture form values in intermediate strings and
primitives; name the inputs with OGNL expressions and the framework reads and writes the properties of the objects they
address, converting on the way. person.name means getPerson().setName(...). The consequences, each verified on the
Bookshelf:
-
The target of a dotted name is created when it is
null. OnNullActionthe propertyreviewis declaredReview review;(never assigned), andreview.text=hiproduces aReview(the framework calls the no-argument constructor andsetReview), then sets the text. AsetReviewmethod must exist, and so must the getter used for the lookup. -
An object that already exists is never replaced.
readyis initialised in the field declaration andready.stars=4sets a property on the existing instance. -
An interface cannot be created.
ratingis declared asRateable(an interface) and isnull, sorating.stars=3is silently discarded.
public class NullAction extends ActionSupport {
private Review review; // class with a public no-arg constructor
private Rateable rating; // interface
private Review ready = new Review(); // already created
@StrutsParameter(depth = 1) public Review getReview() { return review; }
public void setReview(Review v) { review = v; }
@StrutsParameter(depth = 1) public Rateable getRating() { return rating; }
public void setRating(Rateable v) { rating = v; }
@StrutsParameter(depth = 1) public Review getReady() { return ready; }
public void setReady(Review v) { ready = v; }
// report() and execute() omitted
}
$ curl -sg -X POST .../g9typeconv/null.action --data-urlencode review.text=hi \
--data-urlencode rating.stars=3 --data-urlencode ready.stars=4
review = Review{stars=0, text=hi}
rating = null
ready = Review{stars=4, text=null}
The guide spells out the rules: a property declared exactly as Collection or List gets an ArrayList, a Map gets a
HashMap, and a bean with a no-argument constructor is built through the ObjectFactory. Null creation is switched
on only while the params interceptor runs (the context key CREATE_NULL_OBJECTS), so reading review in an action
method does not create one. The @StrutsParameter(depth = 1) on the getters is what permits the request to reach one
level below review: the depth rules belong to the page Parameter Injection and @StrutsParameter. A conversion error inside the nested
object is reported with the full dotted name, review.stars (observed: review.stars=x gives the field error
review.stars).
The guide recommends creating the target objects in the constructor or in a prepare() method when they should not be
built on demand, and notes that the framework "will not instantiate an object if an instance already exists". It also
gives the naming advice for lists and maps: use index notation (people[0].name, friends['patrick'].name), and for a
multiple select box name the element people.name so that the framework creates one Person per selected item.
Creating a Custom Type Converter
When the built-in rules are not right, or the target is a type Struts has never heard of, an application writes a
converter. The guide’s recipe is to extend org.apache.struts2.util.StrutsTypeConverter: two methods, one for each
direction. The Bookshelf has an Isbn value type (a thirteen digit string with a canonical hyphenated form):
public class Isbn {
private final String digits;
public Isbn(String digits) { this.digits = digits; }
public String getDigits() { return digits; }
@Override public String toString() { return "Isbn[" + digits + "]"; }
}
public class IsbnConverter extends StrutsTypeConverter {
@Override
public Object convertFromString(Map context, String[] values, Class toClass) {
String text = values[0].replace("-", "").trim();
if (text.length() != 13 || !text.chars().allMatch(Character::isDigit)) {
throw new TypeConversionException("not an ISBN-13: " + values[0]);
}
return new Isbn(text);
}
@Override
public String convertToString(Map context, Object o) {
String d = ((Isbn) o).getDigits();
return d.substring(0, 3) + "-" + d.substring(3, 4) + "-" + d.substring(4, 6) + "-"
+ d.substring(6, 12) + "-" + d.substring(12);
}
}
The points of the contract:
-
convertFromString(Map context, String[] values, Class toClass)receives all the submitted values of the parameter as an array (a repeated name gives several), andtoClassis the declared type of the property. A converter that serves one type usually readsvalues[0]. -
convertToString(Map context, Object o)is called when a tag or a result needs the property as text, for example<s:property value="isbn"/>or the value of a text field. Without it, the text of anIsbnwould be itstoString(). -
StrutsTypeConverterextendsDefaultTypeConverter, which implements theTypeConverterinterface of the framework. The base class also offers the hookperformFallbackConversion, which the guide describes as a fall-back for when the conversion of the converter fails; by default it delegates to the superclass conversion. A converter that does not need the two-way contract can implementTypeConverterdirectly, or extendDefaultTypeConverterand overrideconvertValue, and signal "not my type" withTypeConverter.NO_CONVERSION_POSSIBLE. -
To report a bad value, throw
TypeConversionException(or, as the guide puts it,XWorkException). The message is for the log, not for the visitor: the visitor sees the text of thexwork.default.invalid.fieldvaluekey (see Conversion Errors). -
The
contextmap is the OGNL context;DefaultTypeConverteroffers the protected methodgetLocale(context), which returns the locale of the currentActionContext(and falls back to the configured locale provider), so a converter can follow the request locale like the built-in ones. -
The converter should be stateless and thread safe, because the framework may reuse one instance for every request.
The same converter on a model that contains a second kind of value, a page range 10-20, shows the pattern for a value
that spans several fields of the target:
public record PageRange(int from, int to) {
public int getFrom() { return from; }
public int getTo() { return to; }
@Override public String toString() { return from + "-" + to; }
}
public class PageRangeConverter extends StrutsTypeConverter {
@Override
public Object convertFromString(Map context, String[] values, Class toClass) {
String[] parts = values[0].split("-");
try {
if (parts.length != 2) {
throw new TypeConversionException("expected from-to");
}
return new PageRange(Integer.parseInt(parts[0].trim()), Integer.parseInt(parts[1].trim()));
} catch (NumberFormatException e) {
throw new TypeConversionException(e.getMessage());
}
}
@Override
public String convertToString(Map context, Object o) { return o.toString(); }
}
Writing the converter is half of the job; the other half is telling Struts when to use it. There are three scopes, and each is a properties file or an annotation.
Applying a Converter: Action, Bean and Application
For one Action
A file named ActionClassName-conversion.properties in the same classpath directory as the action class maps a
property name to a converter class. The Bookshelf’s CustomAction has a range property:
# src/main/resources/org/example/bookshelf/CustomAction-conversion.properties
# syntax: <propertyName> = <converterClassName>
range=org.example.bookshelf.PageRangeConverter
The file is found by the class name of the action, so it belongs in the resources directory of the same package as the
class (org/example/bookshelf/ here). The key can be a nested property, person.phoneNumber=com.acme.PhoneNumberConverter.
For a Bean or Model
When the property belongs to a bean (a model object, a class in a dependency that cannot be modified), the rule file is
named after the bean class and sits next to it on the classpath: ClassName-conversion.properties. The guide’s example is
a Measurement class from a dependency, used through ModelDriven<Measurement>, so that the action cannot hold the
rule: create the same package directory in your own resources and put the file there. In the Bookshelf the Book has a
shelf property of type ShelfCode:
# src/main/resources/org/example/bookshelf/Book-conversion.properties
shelf=org.example.bookshelf.ShelfCodeConverter
For the Whole Application
The key of an application-wide rule is a type, not a property: the rule applies to every property of that type in every
action and bean. The file is struts-conversion.properties in the root of the classpath (WEB-INF/classes); the old name
xwork-conversion.properties is still read but is marked deprecated in the guide.
# src/main/resources/struts-conversion.properties
# syntax: <type> = <converterClassName>
org.example.bookshelf.Isbn=org.example.bookshelf.IsbnConverter
The guide adds that since Struts 7.2.0, when the Spring plugin is active, the converter value can be the name of a Spring
bean (java.math.BigDecimal = myBigDecimalConverter) instead of a class name, so a converter can use injected services.
That sentence is taken from the guide and was not run on the scratch WAR, which has no Spring plugin. The core jar itself
ships one application-wide rule in struts-default-conversion.properties: java.io.File is converted by
org.apache.struts2.conversion.UploadedFileConverter, which is how uploaded files reach File properties (the file
upload page of this section covers it).
The three scopes together, with every converter used and one request:
public class CustomAction extends ActionSupport {
private Isbn isbn; // struts-conversion.properties: Isbn -> IsbnConverter
private PageRange range; // CustomAction-conversion.properties: range -> PageRangeConverter
private Book book; // Book-conversion.properties: shelf -> ShelfCodeConverter (and isbn by the type rule)
public Isbn getIsbn() { return isbn; }
@StrutsParameter public void setIsbn(Isbn v) { isbn = v; }
public PageRange getRange() { return range; }
@StrutsParameter public void setRange(PageRange v) { range = v; }
@StrutsParameter(depth = 1) public Book getBook() { return book; }
public void setBook(Book v) { book = v; }
// report() returns the three properties as text; execute() returns SUCCESS
}
$ curl -sg -X POST .../g9typeconv/custom.action --data-urlencode isbn=978-0-13-468599-1 \
--data-urlencode range=10-20 --data-urlencode book.shelf=B-7 --data-urlencode book.isbn=9780134685991 \
--data-urlencode book.pages=300 --data-urlencode book.title=Dune
isbn = Isbn[9780134685991]
range = 10-20
book = Book{id=null, title=Dune, pages=300, shelf=B-7, isbn=Isbn[9780134685991]}
fieldErrors = {}
$ curl -sg -X POST .../g9typeconv/custom.action --data-urlencode isbn=12 \
--data-urlencode range=x --data-urlencode book.shelf=zz
isbn = null
range = null
book = Book{id=null, title=null, pages=null, shelf=null, isbn=null}
fieldErrors = {isbn=[Bookshelf: invalid value for isbn], range=[Bookshelf: invalid value for range], book.shelf=[Bookshelf: invalid value for book.shelf]}
Note the three details in the second response: all three conversions failed, so all three properties stayed null; the
nested failure is named book.shelf; and the Book object was created (it is printed with its fields null), because
creating the target is separate from converting its values. The message Bookshelf: invalid value for isbn is the
xwork.default.invalid.fieldvalue key overridden in the application’s bundle (see below).
Any Exception Is a Conversion Error
The guide says a converter throws TypeConversionException (or XWorkException) "to allow Struts to recognize that a
conversion error has occurred". The scratch app also has a converter that throws a plain IllegalStateException, wired to a
boom property through CustomAction-conversion.properties; the request is answered like any other conversion error and
nothing leaks to the container:
$ curl -sg -X POST .../g9typeconv/custom.action --data-urlencode boom=1-2 --data-urlencode range=1-2
isbn = null
range = 1-2
book = null
boom = null
fieldErrors = {boom=[Bookshelf: invalid value for boom]}
So the safe reading of the contract is that a converter signals a bad value by throwing, and that the framework treats any
RuntimeException the same way, but a converter that wants a clean log and a precise exception type should throw
TypeConversionException (or its subclasses) itself.
Collections and Maps
A form often edits several objects at once: the three books of a wish list, a table of notes keyed by book id. Struts
supports this through the index notation of OGNL names and through the Element_, Key_, KeyProperty_ and
CreateIfNull_ rules of a -conversion.properties file. The ObjectTypeDeterminer reads those rules (the default
implementation is DefaultObjectTypeDeterminer; a custom one can be registered), and where the property is declared with
generics the determiner reads the type argument.
Index Notation With Generics
With generics the rule files are not needed at all, because the type arguments say what to build. CollectionAction is
the test bed:
public class CollectionAction extends ActionSupport {
private List<Book> books = new ArrayList<>();
private Map<String, Book> shelves = new HashMap<>();
private Map<Long, String> notes = new TreeMap<>();
private Collection<Book> catalogue = new ArrayList<>();
private Set<String> genres;
private Book[] picks = new Book[3];
@StrutsParameter(depth = 2) public List<Book> getBooks() { return books; }
public void setBooks(List<Book> v) { books = v; }
@StrutsParameter(depth = 2) public Map<String, Book> getShelves() { return shelves; }
public void setShelves(Map<String, Book> v) { shelves = v; }
@StrutsParameter(depth = 1) public Map<Long, String> getNotes() { return notes; }
public void setNotes(Map<Long, String> v) { notes = v; }
// catalogue, genres and picks follow; execute() returns SUCCESS
}
$ curl -sg -X POST .../g9typeconv/collection.action \
--data-urlencode "books[0].title=A" --data-urlencode "books[1].pages=5" \
--data-urlencode "shelves['top'].title=T" --data-urlencode "shelves.low.title=L" \
--data-urlencode "notes[7]=seven" --data-urlencode "genres=a" --data-urlencode "genres=b" --data-urlencode "genres=a"
books = [Book{id=null, title=A, ...}, Book{id=null, title=null, pages=5, ...}]
shelves = {top=Book{id=null, title=T, ...}, low=Book{id=null, title=L, ...}}
notes = {7=seven}
genres = [a, b]
What the output shows. A List<Book> is addressed with books[0], books[1], and the missing elements are created and
converted from the generic argument. A Map<String,Book> accepts both shelves['top'] and the shorter shelves.low;
the key 7 of Map<Long,String> was converted to a Long by the same mechanism. A Set has no index; repeated values
are collapsed. If a list index skips ahead the gaps are filled with null: submitting only books[255].title=ok yields a
list of 256 elements of which the first 255 are null (observed, see the growth limit below), so templates must
tolerate null elements. Elements that failed to convert are reported by their full name:
books[0].pages=zz gives the field error books[0].pages.
Element_, Key_, KeyProperty_ and CreateIfNull_
Where generics are not available, or where the collection is addressed by something other than its position, the rules
go into ActionClassName-conversion.properties (or the conversion file of whatever bean holds the property):
| Rule | Meaning |
|---|---|
|
The class of the elements of a |
|
The class of the keys of a |
|
The property of the element that identifies it, so that an element is addressed by that property’s value with the |
|
Create elements, and the collection itself, when they are missing (the default is |
The Bookshelf uses catalogue, a Collection<Book> that is edited by book id:
# src/main/resources/org/example/bookshelf/CollectionAction-conversion.properties
KeyProperty_catalogue=id
Element_catalogue=org.example.bookshelf.Book
CreateIfNull_catalogue=true
$ curl -sg -X POST .../g9typeconv/collection.action \
--data-urlencode "catalogue(5).title=Five" --data-urlencode "catalogue(5).id=5" --data-urlencode "catalogue(6).title=Six"
catalogue = [Book{id=5, title=Five, ...}, Book{id=6, title=Six, ...}]
Source: Core Developer Guide — Type Conversion, Indexing a collection by a property of that collection
The () notation (parentheses, not brackets: "Notice the () notation! Do not use [] notation, which is for Maps only", in
the words of the guide’s example) says "the element whose id is 5". The value in the parentheses is converted to the
type of the key property (Long here), and the element is looked up, or created when CreateIfNull is on, with the key
property set to it. The book with id=6 came into existence although only its title was submitted, because the key in the
name set its id. The guide stresses the practical gain: a row of a table stays tied to its identifier, so a form can edit
items in any order, or after another user deleted one, without the index shift that books[2] suffers. The page of the guide uses an iterator:
<s:form action="collection" theme="simple">
<s:iterator value="catalogue" var="bean">
<s:textfield name="%{'catalogue(' + #bean.id + ').title'}" value="%{#bean.title}"/>
</s:iterator>
</s:form>
Rendered by the scratch app for the request that set books 5 and 6, the page contains one input per element, named by id:
<input type="text" name="catalogue(5).title" value="Five" id="collection_catalogue_5__title"/>
<input type="text" name="catalogue(6).title" value="Six" id="collection_catalogue_6__title"/>
The guide’s version of that listing writes id="bean", <stextfield and the name beanList(%{bean.id}).name; the typo
and the retired id attribute (replaced by var since 2.1.x) are listed under the inconsistencies.
makeNew, the Creation of Elements and the Growth Limit
An element addressed by key is not created when it does not exist, unless CreateIfNull is on: fooCollection(22) that
is missing is simply absent, "to avoid OutOfMemoryErrors". For adding new elements the guide offers the notation
fooCollection.makeNew[0], fooCollection.makeNew[1]. On the Bookshelf:
$ curl -sg -X POST .../g9typeconv/collection.action \
--data-urlencode "catalogue.makeNew[0].title=Phil" --data-urlencode "catalogue.makeNew[1].title=John"
catalogue = [Book{id=null, title=Phil, ...}, Book{id=null, title=John, ...}]
Source: Core Developer Guide — Type Conversion, Indexing a collection by a property of that collection
The guide’s caveat applies to a Set: equals and hashCode of the element must not rest only on the id, or two new
elements, both with a null id, collapse into one.
An index is an invitation to allocate memory, so the growth is limited. The constant
struts.ognl.autoGrowthCollectionLimit (it replaced the deprecated xwork.autoGrowCollectionLimit) caps the highest
index an indexed name can create. It is not set in the default properties file in 7.4.0; the documented default is
255, which means that 256 elements are allowed. The behaviour on the Bookshelf agrees with that:
books[255].title=ok -> books has 256 elements (255 nulls, then the book)
books[256].title=nope -> books = [] (the parameter is dropped; no field error, no exception)
books[300].title=nope -> books = []
The limit exists to prevent a denial of service by a parameter named books[2000000000].title. A form that really needs
more rows raises it in struts.xml: <constant name="struts.ognl.autoGrowthCollectionLimit" value="1024"/>. Note
that going over the limit is silent: the data is lost without a conversion error or a log line at the default level, so an
unexpectedly empty list is worth checking against this limit.
What Does Not Convert
-
A map whose key cannot be converted does not report an error:
notes[x]=badwithMap<Long,String>leavesnotesempty (observed; comparebooks[0].pages=zz, which is reported). -
An array of beans does not grow:
picks[1].title=Pon a pre-sizedBook[3]left every elementnull(observed with@StrutsParameter(depth = 2)on the getter). Use aList<Book>for repeated beans. -
The elements of a raw
ListareStringwhen noElement_rule is present (the guide: "it is assumed to be a String"). -
A property declared with a custom interface cannot be instantiated (the
ratingexample above); initialise the field or create the object in aprepare()method. -
Generics lost to erasure break conversion in abstract hierarchies. The guide’s example is
Measurement<T extends Enum>with a settersetUnits(T): the signature after erasure issetUnits(java.lang.Enum), the framework tries to instantiateEnumand fails withIllegalArgumentException: java.lang.Enum is not an enum type. The fix is to work around the erasure with a property of the concrete type. The guide’s example was not run here; the rule it states (that the converter sees only the erased parameter type) is the one the numbers above follow.
Conversion Annotations
Everything a -conversion.properties file says can be written as annotations on the action. The class carries
@Conversion; each rule is a @TypeConversion, on a method, and since Struts 7.3.0 also on a field.
| Attribute | Default | Meaning |
|---|---|---|
|
none |
Marker at the type level; the optional array holds rules that apply to the class (keys must then name the property) |
|
empty (the annotated property’s name is used) |
The property (for |
|
|
|
|
|
One of |
|
|
The converter class; for an |
|
empty |
The converter class name as a string; marked deprecated in the guide in favour of |
|
empty |
The value of a |
The Bookshelf’s AnnotatedAction uses the three placements: a setter with a string converter name, a field, and a list
element rule.
@Conversion
public class AnnotatedAction extends ActionSupport {
private PageRange range;
@TypeConversion(converterClass = ShelfCodeConverter.class) // on a field: since 7.3.0
private ShelfCode shelf;
private List<Book> wishlist = new ArrayList<>();
public PageRange getRange() { return range; }
@StrutsParameter
@TypeConversion(converter = "org.example.bookshelf.PageRangeConverter") // on a setter, string name
public void setRange(PageRange v) { range = v; }
public ShelfCode getShelf() { return shelf; }
@StrutsParameter public void setShelf(ShelfCode v) { shelf = v; }
@StrutsParameter(depth = 2)
@TypeConversion(rule = ConversionRule.ELEMENT, converterClass = Book.class) // elements of the list
public List<Book> getWishlist() { return wishlist; }
public void setWishlist(List<Book> v) { wishlist = v; }
// report() and execute() omitted
}
$ curl -sg -X POST .../g9typeconv/annotated.action --data-urlencode range=3-9 --data-urlencode shelf=C-2 \
--data-urlencode "wishlist[0].title=X" --data-urlencode "wishlist[1].pages=7"
range = 3-9
shelf = C-2
wishlist = [Book{id=null, title=X, ...}, Book{id=null, title=null, pages=7, ...}]
fieldErrors = {}
$ curl -sg -X POST .../g9typeconv/annotated.action --data-urlencode range=q --data-urlencode shelf=Z
range = null
shelf = null
wishlist = []
fieldErrors = {range=[Bookshelf: invalid value for range], shelf=[Bookshelf: invalid value for shelf]}
Three details of the annotations, taken from the Struts 7.3.0 changes and the guide:
-
Key derivation. Until 7.3.0 the rule prefix was added automatically only for annotations on methods; a class-level
@Conversion(conversions = ...)had to writekey = "CreateIfNull_users". From 7.3.0 the key is the bare property name for every rule and the prefix (CreateIfNull_,Element_,Key_,KeyProperty_) is derived. A key that already carries a known prefix is used as is and is never prefixed twice; the keys ofConversionType.APPLICATIONrules are class names and are never prefixed. -
Field placement. Since 7.3.0
@TypeConversioncan be placed on a field, asshelfabove, which keeps the rule next to the declaration. -
COLLECTIONis deprecated.ConversionRule.COLLECTIONand theCollection_prefix are deprecated (WW-5656); useELEMENT, which "additionally covers the values of a Map". The engine still readsCollection_xxxas a fallback.
Source: Struts Version Notes 7.3.0
Conversion Errors
A conversion error does not stop a request; it leaves a trail. This section follows that trail from the converter to the
form, with the Bookshelf’s EditAction behind a form of four fields.
The conversionErrors Map and the conversionError Interceptor
Each failed conversion is stored in the map conversionErrors of the ActionContext, keyed by the property name, with the
rejected value and the target class. Nothing is reported yet. The interceptor conversionError (class
StrutsConversionErrorInterceptor in struts-default.xml, a subclass of ConversionErrorInterceptor) reads the map
after params has run and, for each entry, if the action is ValidationAware:
-
adds a field error with the text of the key
xwork.default.invalid.fieldvalue(the default text of 7.4.0 isInvalid field value for field "{0}".) or the per-field overrideinvalid.fieldvalue.<field>; -
remembers the original value, so that any later read of the property from a tag returns what the visitor typed, not the value in the action (
abcstaysabc, not0); -
does nothing for a value that is
null, the empty string or an array holding only the empty string, which is the variant that the Struts subclass adds so that an empty text field is not reported as an error.
The interceptor has no parameters of its own, extends MethodFilterInterceptor (so includeMethods and excludeMethods
work) and is part of basicStack, defaultStack and paramsPrepareParamsStack. After it, validation can add more
errors, and workflow returns input if there are any.
A Form That Keeps What the Visitor Typed
The edit mapping returns input to edit.jsp when conversion fails. The action has four properties with conversion
rules (a primitive int, a Date, a BigDecimal and the Isbn of the application-wide converter), and two message keys:
the global bundle global.properties, registered with the constant struts.custom.i18n.resources, overrides the generic
message, and EditAction.properties overrides one field:
# src/main/resources/global.properties
xwork.default.invalid.fieldvalue=Bookshelf: invalid value for {0}
# src/main/resources/org/example/bookshelf/EditAction.properties
invalid.fieldvalue.pages=Pages must be a whole number.
<constant name="struts.custom.i18n.resources" value="global"/>
<package name="default" extends="struts-default" namespace="/">
<action name="edit" class="org.example.bookshelf.EditAction">
<result>/WEB-INF/views/report.jsp</result>
<result name="input">/WEB-INF/views/edit.jsp</result>
</action>
</package>
<%@ taglib prefix="s" uri="/struts-tags" %>
<s:fielderror/>
<s:form action="edit" theme="simple">
<s:textfield name="pages"/>
<s:textfield name="published"/>
<s:textfield name="listPrice"/>
<s:textfield name="isbn"/>
<s:submit/>
</s:form>
Submitting four invalid values returns the form again, with the errors and with the invalid text in the inputs (the output of
curl for pages=abc published=notadate listPrice=1.5x isbn=bad, whitespace shortened):
<ul class="errorMessage">
<li><span>Pages must be a whole number.</span></li>
<li><span>Bookshelf: invalid value for isbn</span></li>
<li><span>Bookshelf: invalid value for published</span></li>
<li><span>Bookshelf: invalid value for listPrice</span></li>
</ul>
<form id="edit" name="edit" action="/g9typeconv/edit.action" method="post">
<input type="text" name="pages" value="abc" id="edit_pages"/>
<input type="text" name="published" value="notadate" id="edit_published"/>
<input type="text" name="listPrice" value="1.5x" id="edit_listPrice"/>
<input type="text" name="isbn" value="bad" id="edit_isbn"/>
<input type="submit" value="Submit" id="edit_0"/>
</form>
Four things to read from it. The override invalid.fieldvalue.pages won over the global message for that one field. The
original texts abc, notadate and 1.5x are back in the inputs, although the properties themselves are 0 and null.
The order of the messages is not the order of the fields (the errors come from a map), so a page must not depend on it. And
the escaping is the standard one of the tags: the interceptor escapes the original value for OGNL (escapeJava), and the
s:textfield tag escapes it for HTML.
The per-field message may use the message formatting of the localization system, with the field label as {0}. Both the
generic key and the per-field key go in the usual resource bundles of the action (the page Localization and i18n
covers the search order), so a different language is a matter of adding global_es.properties with the same keys.
Keeping Errors Out: excludeMethods and the Validator Route
The guide: "To keep conversion errors from reporting globally, change the interceptor stack, and add additional validation
rules." The two routes are checked on StrictAction, which has an Integer pages and an int copies, mapped twice.
The first mapping, strict, uses the default stack but excludes execute from conversionError:
<action name="strict" class="org.example.bookshelf.StrictAction">
<interceptor-ref name="defaultStack">
<param name="conversionError.excludeMethods">execute</param>
</interceptor-ref>
<result>/WEB-INF/views/report.jsp</result>
<result name="input">/WEB-INF/views/report.jsp</result>
</action>
$ curl -sg -X POST .../g9typeconv/strict.action --data-urlencode pages=abc --data-urlencode copies=zz
pages = null
copies = 0
fieldErrors = {}
No error is raised, the invalid input is silently replaced by the default, and the action method runs with null and 0.
That is the choice that "conversion errors are not an error" implies, and it is almost never what a form wants; the case for it
is an action that does its own checking. The second mapping, lenient, keeps the default stack and adds a rule file with a
conversion validator for one field:
<!-- StrictAction-lenient-validation.xml -->
<validators>
<field name="pages">
<field-validator type="conversion">
<message>Pages: a whole number is required</message>
</field-validator>
</field>
</validators>
$ curl -sg -X POST .../g9typeconv/lenient.action --data-urlencode pages=abc --data-urlencode copies=zz
fieldErrors = {copies=[Bookshelf: invalid value for copies],
pages=[Bookshelf: invalid value for pages, Pages: a whole number is required]}
With both the interceptor and the validator the field gets two messages: the generic one from conversionError and the
validator’s own. The Struts constant struts.validators.skipValidatorsOnConversionError (default false in 7.4.0; the
Version Notes of 7.3.0 list the ticket WW-2934, "conversion error should short-circuit all validators") skips the
other validators of a field that failed to convert, to avoid a second message of a "required" or "range" rule for a value
that never became a number; as the comment of default.properties says, the field’s own conversion validator still runs.
The validator also takes the parameter repopulateField: set to true, it re-fills the stack with the original value for
a form that does not use the conversionError interceptor. The validation page of this section describes the validator
framework itself.
What a Conversion Error Does Without an input Result
Because workflow returns input when there are field errors, a mapping without an input result (or with the
action’s execution still reachable) fails in the way the page Exception Handling and Global Results shows: with a global
java.lang.Exception mapping the failure to find the input result is swallowed into the polite error page with HTTP
200, which is a confusing way to learn that an id=abc was typed. Map input for every action that receives request
parameters (see Exception Handling and Global Results).
Common Problems at a Glance
| Symptom | Cause | Fix |
|---|---|---|
A checkbox or "yes" value arrives as |
Boolean conversion is |
Give the checkbox |
A price |
The comma is the grouping separator of |
Decide the locale of input; use a validator for a pattern, or a custom converter with a strict |
|
Primitives use the plain converter without grouping; wrappers use |
Use one family consistently |
A date typed as |
The |
Define |
The same date means two days to two visitors |
SHORT dates follow the locale of the request |
Do not accept ambiguous numeric dates; use a date picker that sends ISO |
A list stays empty after a large form (observed) |
|
Raise the constant, or paginate the form |
A |
A key that cannot be converted is dropped without an error |
Validate keys; use |
An array of beans does not fill (observed) |
Array elements are not created from indexed names |
Use |
|
An interface property cannot be instantiated |
Initialise it, or create it in |
|
The Struts variant ignores |
Use a required rule in validation |
A bad value is replaced by |
The action’s stack excludes |
Keep |
Two messages for one field (observed) |
|
Use one of them, or set |
A custom converter has no effect |
The conversion properties file is in the wrong package, has the wrong name ( |
Put it in the class’s package, directly under |
A converter is called for a type it was not written for |
An application-wide rule names a type, so it applies everywhere |
Narrow it to a property rule, and check |
The application cannot assign a property although a converter exists |
No |
See the parameter injection page; this is not a conversion problem |
A converter throws a plain exception and the log is noisy (observed: the visitor still sees only a field error) |
Any |
Throw |
What Changed Recently
| Version | Change |
|---|---|
2.5.2 |
|
7.0.0 |
All |
7.0.0 (the |
Properties can be set from the request only through annotated setters; the examples of older books without the annotation do nothing |
7.2.0 |
With the Spring plugin, |
7.3.0 |
|
before 7.4.0 (version not stated in the guide) |
The growth limit constant is |
Source: Struts Version Notes 7.3.0
Source: Struts Version Notes 7.0.0
Where the Books Are Outdated
The books of the section teach the idea correctly: request parameters named with OGNL, converters registered by properties files, conversion errors collected and shown beside the field. The details have moved.
| In the books | Struts 7.4.0 |
|---|---|
Converters extend |
|
|
|
|
|
|
|
Conversion annotations at method level only, with the rule prefix spelled out in class-level |
Field placement and key derivation (7.3.0) |
Conversion errors reported as |
The key |
Dates "use the SHORT format of the locale" |
A list of eight formats, including ISO forms (see Dates) |
Request parameters fill any public setter of the action |
Only setters annotated |
Struts 1 |
Struts 2 converts by itself into the typed property of the action or model; there is no |
Source: Struts Version Notes 7.0.0
Inconsistencies in the Official Documentation
Collected while running the examples against 7.4.0; none changes behaviour, but each can mislead:
-
The Type Conversion page says dates use "the SHORT format for the Locale";
DateConvertertries eight formats andjava.timetypes use ISO formats (see Dates). -
The page says a blank string "cannot be set on a BigDecimal or BigInteger"; in 7.4.0 a blank string gives
nullwithout an error (observed). -
The Null Property Handling section refers to
com.opensymphony.xwork2.interceptor.ParametersInterceptor; the class isorg.apache.struts2.interceptor.parameter.ParametersInterceptorin 7.x. The same section also says "set it by calling setUsers() and then finally called getUsers().setName()" for thepersonexample (the setter issetPerson). -
The Advanced Example listing uses
<stextfield name="beanList(%{bean.id}).name" />(the tag prefix is missing a colon) andid="bean"ons:iterator(the retired attribute;varis current). -
The TypeConversion Annotation page’s last example writes
property = "java.util.Date"; the 7.4.0 annotation has no attributeproperty(its attributes arekey,type,rule,converter,converterClassandvalue, perjavap), and the page’s parameter table listskeyandvaluebut describesvalueonly forKEY_PROPERTY. -
The parameter table of the TypeConversion Annotation page marks
converteras "DEPRECATED: either this or value" while the Javadoc of the annotation in the 7.4.0 jar carries defaults for every attribute (keyandconverterandvalueare the empty string,converterClassisXWorkBasicConverter); the exampleconverterClass = String.classfor anELEMENTrule shows that the attribute doubles as the element class. -
The Conversion Annotation page’s example (
public setDate(Date date)) has a setter without a return type, which does not compile. -
The page title of the guide page for annotations in older navigation,
conversion-annotations, no longer exists (the address returns404); the pages areconversion-annotationandtype-conversion-annotation.
Summary
-
Conversion is a service of the OGNL layer:
paramssets properties by name, the converter turns text into the declared type, and failures are recorded inconversionErrors, not thrown. -
The built-in converters are locale aware for numbers and dates; booleans are
Boolean.valueOf; blank input is a non-error for most targets; the date format list is broader than the guide says. -
Custom converters extend
StrutsTypeConverterand throwTypeConversionException; they are registered per action (Action-conversion.properties), per bean (Class-conversion.properties) or per type for the whole application (struts-conversion.properties), or with@Conversionand@TypeConversion. -
Collections and maps are filled from index notation;
Element_,Key_,KeyProperty_andCreateIfNull_describe what the generics cannot; the()notation addresses an element by key; growth is limited bystruts.ognl.autoGrowthCollectionLimit. -
The
conversionErrorinterceptor turns the recorded failures into field errors withxwork.default.invalid.fieldvalueorinvalid.fieldvalue.<field>, and keeps the text the visitor typed;workflowreturnsinput. -
The failure modes that lose data silently (comma as grouping, a date prefix, the growth limit, an excluded interceptor) are the ones to test for in a real form.
The related pages of this section are the value stack and OGNL (names and the expression syntax), parameter injection (what the request may set), validation (what to check after the types are right) and localization (the locale and the message bundles that this page uses), all linked in the introduction. Further related pages are Actions, Interceptors, Configuration, struts.xml and Constants and, for how JSP’s Expression Language coerces values (a different mechanism with a similar vocabulary), EL Operators and Type Coercion.
References
-
Core Developer Guide — Type Conversion, Built in Type Conversion Support
-
Core Developer Guide — Type Conversion, Relationship to Parameter Names
-
Core Developer Guide — Type Conversion, Creating a Type Converter
-
Core Developer Guide — Type Conversion, Applying a Type Converter to an Action
-
Core Developer Guide — Type Conversion, Applying a Type Converter to a bean or model
-
Core Developer Guide — Type Conversion, Applying a Type Converter for an application
-
Core Developer Guide — Type Conversion, Null Property Handling
-
Core Developer Guide — Type Conversion, Collection and Map Support
-
Core Developer Guide — Type Conversion, Indexing a collection by a property of that collection
-
Core Developer Guide — Type Conversion, An advanced example for indexed Lists and Maps
-
Core Developer Guide — Type Conversion, Auto growth collection limit
-
Core Developer Guide — Type Conversion, Type Conversion Error Handling
-
Core Developer Guide — Type Conversion, Null and Blank Values
-
Core Developer Guide — Type Conversion, Generics and Erasure
-
Core Developer Guide — TypeConversion Annotation, Parameters
-
Core Developer Guide — TypeConversion Annotation, Key derivation
-
Core Developer Guide — conversion validator, Repopulating Field upon conversion Error
-
Core Developer Guide — Interceptors, Interceptor Parameter Overriding