Skip to main content

Converters

Converters can be thought of as "post-processors" for response POJOs before they get passed to the serializer.

Converters are associated with resource classes and methods via the following:

RestconvertersRestOpconverters
Example
// GET person request handler.
// Traversable conversion enabled to allow nodes in returned POJO tree to be addressed.
// Queryable conversion enabled to allow returned POJO to be searched/viewed/sorted.
@RestGet(
path="/people/{id}/*",
converters={Traversable.class,Queryable.class}
)
public Person getPerson(@Path("id") int id) {
return findPerson(id);
}

Juneau defines the following converters out-of-the-box:

RestConverterQueryable - Provides BeanQuery search/view/sort/paging parameters that transform the response before it is serialized.Traversable - Allows nodes in the POJO response tree to be individually accessed through additional path info on the request.Introspectable - Allows method calls to be made on the response POJO, and for the result of that method call to be serialized as the response.

Queryable (BeanQuery)​

As of 10.0, Queryable runs on the shared BeanQuery engine. A response that is a Collection or array of beans (or of Maps) can be searched, projected, sorted and paged with these request parameters, which are bound by BeanQueryRequest:

ParameterMeaning
searchComma-separated column=expression clauses, e.g. name=$contains(smith),age=$gte(18)
viewComma-separated columns to return, in order. Rows come back as maps of those columns
sortComma-separated sort columns; append :desc (or -) for descending
position0-based index of the first row (negative is HTTP 400)
limitMaximum rows. Defaults to defaultLimit (100) and is clamped to maxLimit (1000)
optskey=value options, e.g. counts=true
GET /people?search=age=$gte(18)&sort=name,age:desc&view=name,age&position=20&limit=10

A response that is not tabular (not a collection or array, or whose elements are plain strings, numbers, dates or other scalars) is returned unchanged and the parameters are ignored. An empty collection is also returned unchanged. A bean type gets one cached context per element class; Map rows get a context per request, with one column per key of the first row (types inferred from that row's values).

A bad column or option (for example ?sort=noSuchColumn) throws BeanQuerySyntaxException, which the server maps to HTTP 400 with an X-BeanQuery-Error: UNKNOWN_COLUMN header (the BeanQuerySyntaxException.Code name) and, with problem details on, a "code" member in the body. A BeanQueryExecutionException becomes HTTP 500 with the body Query execution failed.; the cause is logged, not sent. See BeanQuery: REST binding.

Example — counts

?opts=counts=true adds count headers. The body stays a bare list, and each header is set only when that count was computed:

GET /people?search=age=$gte(18)&opts=counts=true

X-BeanQuery-Total: 42
X-BeanQuery-Matched: 7

QueryableSettings​

Register a QueryableSettings bean to tune the engine per resource. Each setting you leave unset keeps the BeanQuery default, so the defaults are not duplicated here.

Example
@Rest(converters=Queryable.class)
public class PeopleResource extends BasicRestServlet {

@Bean
public QueryableSettings queryableSettings() {
// $regex is allowed by default; allowRegex(false) opts this resource out.
return QueryableSettings.create().defaultLimit(20).maxLimit(500).build();
}

@RestGet("/people")
public List<Person> getPeople() {...}
}
SettingDefaultMeaning
defaultLimit(int)100Page size when the request has no limit
maxLimit(Integer)1000Largest page size; larger requests are clamped (null = no cap)
allowRegex(boolean)trueThe $regex operator; false makes it a 400 (REGEX_DISABLED)
regexTimeout(Duration)50 msTime budget for all regex matching in one query
countPolicy(CountPolicy)IF_REQUESTEDWhich counts are computed
maxSearchClauses / maxSortKeys / maxSearchLength / maxExpressionDepth64 / 8 / 4096 / 16Caps on the request

All of these are trusted server-side settings; none is ever read from request data.

Your own list operation​

Without the converter, take a BeanQueryRequest argument and run the engine yourself. The argument binds all six parameters and documents them in Swagger automatically:

private static final InMemoryBeanQueryContext<Person> PEOPLE = InMemoryBeanQueryContext.create(Person.class).build();

@RestGet("/people")
public List<Person> getPeople(BeanQueryRequest q) {
try (var s = PEOPLE.getSession(people)) {
return s.find(q).rows();
}
}

A concrete @Request class like BeanQueryRequest must have a public no-arg constructor and a setter for every annotated getter; this is checked when the operation table is built (see the V10 Migration Guide).

DataTables server-side processing​

Example — DataTables server-side processing

The optional juneau-console-datatables module ships a DataTablesRequest bean that models the DataTables server-side request (draw/start/length/search/order/columns), and a DataTablesQuery adapter that maps it onto a BeanQuery session and returns a DataTablesResults envelope.

private static final InMemoryBeanQueryContext<MyRow> ROWS = InMemoryBeanQueryContext.create(MyRow.class).build();

@RestPost(path="/rows")
public DataTablesResults<MyRow> getRows(@Content DataTablesRequest request) {
try (var session = ROWS.getSession(loadRows())) {
return DataTablesQuery.run(request, session);
}
}

The caller owns the session, so the same adapter works over the in-memory engine or a SQL-backed one. A malformed search expression throws BeanQuerySyntaxException, which propagates to HTTP 400 with the X-BeanQuery-Error header; JuneauDataTables.ajax shows the server's message to the user. See the BeanQuery package topic for the engine itself.

Client-side helpers (DataTables)​

The juneau-console-datatables module also ships thin, first-party browser-side helpers so a server-rendered table can be upgraded into a DataTables grid without hand-duplicating column lists or serving the wiring yourself:

DataTablesColumns - Generates the DataTables columns array (one data/title/orderable/searchable entry per column) from a bean's property order, using @Schema(title) titles (falling back to a humanized property name), so the browser-side column list isn't duplicated from the server bean.DataTablesTable - Builds a plain, DataTables-ready HTML <table> DOM bean (stable id, clean <thead>/<tbody>, marked with data-juneau-datatable) from a collection of rows. Rendered by any Juneau HTML serializer — no coupling to the marshall-core HtmlDocSerializer.DataTablesMixin - A @Rest(mixins=...) mixin that serves the first-party juneau-datatables.js glue script at /juneau-datatables.js. The glue auto-initializes every <table data-juneau-datatable> on the page by calling $(table).DataTable(opts) once the DataTables library is present.
DataTables library is caller-provided

This is an Apache project, and the DataTables library's license is not an ASF category-A license. Juneau therefore does not bundle DataTables' own JS/CSS (nor jQuery) — the only asset it ships is the thin first-party juneau-datatables.js glue. Reference the DataTables library from a CDN or self-host it, and wire it into the page yourself via @HtmlDocConfig. @HtmlDocConfig is resolved per endpoint / per resource-class hierarchy — a mixin cannot inject page assets into your other endpoints — so add the wire-points to the resource (or op) that renders the table:

@Rest(path="/releases", mixins=DataTablesMixin.class)
@HtmlDocConfig(
stylesheet="$S{juneau.datatables.cssUrl:https://cdn.datatables.net/2.1.8/css/dataTables.dataTables.min.css}",
head={
"<script src='$S{juneau.datatables.jqueryUrl:https://code.jquery.com/jquery-3.7.1.min.js}'></script>",
"<script src='$S{juneau.datatables.jsUrl:https://cdn.datatables.net/2.1.8/js/dataTables.min.js}'></script>",
"<script src='servlet:/juneau-datatables.js'></script>"
}
)
public class ReleasesResource extends BasicRestServlet {
@RestGet
public Table getReleases() {
return DataTablesTable.of("releases", releases, Release.class);
}
}

The URLs are SVL-resolvable, so the DataTables distribution can be overridden per environment without code changes.