This release adds server-sent events, htmx support, CSRF protection, database migrations and JSON, and improves GraalVM native image support.
Existing applications should read the compatibility notes at the end.
Web engine
- Added server-sent events with broadcasting, history replay, htmx and JSON integration, bridged to the generic query manager and the workflow engine.
- Added
SseConnectionFilterto select the connections thatSseBroadcaster.sendandcloseact on, andSseErrorListenerto report event conversion and broadcasting failures, which is whatSseGqmBridge.onErrorregisters. - Added htmx support: request accessors, response headers with trigger accumulation, location context and history suppression, and
Varyhandling throughContext.varyOn. - Added
Context.printBlockto write a single template block, andContext.printHtmxFragmentto serve either the full page or an htmx fragment, including the history restoration guard and the matchingVaryheaders. - Added opt-in CSRF protection with SameSite cookie support and configurable cookie, parameter and header names.
- Added the
context:csrfTokenandcontext:htmxHeaderstemplate values, androute:inputs:tags now add a hidden CSRF parameter when a token is active for the request. - Added the
SameSiteenum andCookieBuilder.sameSite, so any cookie an application sets can carry aSameSitepolicy, andAuthConfig.cookieSameSiteto set it for the authentication and remember-me cookies. - Added
Router.destroy(), which is called for every grouped router when a site shuts down. Prior to 1.10 this method only existed onSiteand only ran for the top-level site. - Added
Server.connectionIdleTimeoutandTomcatServer.connectionTimeout, and the embedded servers mark their RIFE2 filter and default servlet as async supported, so that long-lived event streams stay open. A war deployment that uses detached SSE connections has to add<async-supported>true</async-supported>to the RIFE2 filter declaration inweb.xml, and to every other filter and the servlet in the request's chain. - Added bean population restricted to a validation group or to an explicit collection of properties. A selected property whose parameter is absent is reset to the value of a new bean instance rather than left alone, and a boolean becomes
false, since browsers don't send unchecked checkboxes. A file property keeps its content when no upload arrived. - Added a paged navigation variant that carries extra parameters in every link.
- Fixed #58: multipart requests failing under Jakarta EE 11.
- Fixed
@Configlist injection to resolve the generic element type against the concrete element class, so an inheritedList<T>field receives converted values instead of raw strings.
Database
- Added declarative database migrations, with reversible migrations through
ReversibleDbMigrationandpreviewandpreviewFromcounterparts tomigrateandmigrateFrom. The resource that holds the migration state is configurable throughRifeConfig.migrations(), and the rollback methods return the version the schema actually reached. - Added the
AlterTable,CreateIndex,DropIndex,CreateView,DropViewandTruncatequery builders, bean metadata support in the DDL builders and multiple drops. - Added the
columnNameconstraint to map a bean property to a differently named column (rife2/rife2-core#4). - Added the
fileconstraint for properties that receive their content through uploads. - Added parameterized where clauses to the generic query manager restore and count queries.
- Added
DbConnection.getTransactionIsolation. Transactions with a custom isolation level restore the previous level when they complete, and nested transaction users inherit the enclosing isolation. - Transactions now complete consistently for every exception and error that ends them, so the control flow that
redirectandrespondare built on commits instead of rolling back. - Connections are released on every path when the datasource has no transactions.
- Fixed generic query manager relationship element detection to resolve an inherited generic type argument against the concrete bean class, where the type variable previously caused a
ClassCastException.
Beans, validation and forms
- Added transparent support for
Optionalbean properties, which are unwrapped wherever a property is read or written (#54, rife2/rife2-core#7). - Added JSON parsing, generation and bean conversion.
Json.toStringconverts beans and records automatically, and JSON templates now use the dedicatedBeanHandlerJson, which honors the newserializedconstraint. - Added type variable resolution to
ClassUtilsthroughresolveTypeVariableanderasedType. - Added
BeanUtils.parsesWithFormat,formatPropertyValueForInputandparseInputValue. - Form generation writes each field value the way that field reads it back, including a constraint's
defaultValue. - Text read through a constraint's
formatmust now be consumed whole and must fit the property. - Cloning a constrained property copies the constraint values that can be cloned, instead of sharing all of them.
Continuations and native images
- Reworked continuation instrumentation around stack-map frames.
- Added ahead-of-time instrumentation through
InstrumentationDeployeras an alternative to the java agent. - Improved GraalVM native image support with build-time reflection config generation. CI verifies that the resources RIFE2 ships stay reachable both on the module path and in a native image.
- Continuation failures are no longer treated as control flow exceptions.
- Fixed metadata instrumentation for wide arguments and parameterized constructors, and fixed the lazy-load accessor frame computation to use the right class loader.
- Updated the bundled ASM, which reads class files up to Java 27.
Workflow
- Added error handling with error listeners to the workflow engine. Work that ends with an exception is now reported instead of being swallowed, and no longer leaves the workflow waiting forever.
Workflow.waitForNoWorknow loops until all work has finished, andwaitForPausedWorkreturns whether it stopped on paused work.- Event listeners are notified before paused work resumes, so a listener receives the triggering event before any event that the resumed work triggers.
- Fixed races that could strand a pending event, or report completion before resumed work had finished.
- The workflow engine moved from an alpha experimental stage to a beta stage.
Out-of-container testing
MockConversationimplementsAutoCloseableand exposesdestroy, so that the teardown lifecycle of a site can be tested out of container.- Added
MockRequest.htmx,MockResponse.getEventsandMockEventfor asserting on htmx requests and event streams. - Mock cookies copy their attributes, so
SameSitesurvives a round trip.
Other
- Added
DirectoryResourcesfor writing resources as files in a directory. - Added
rife.tools.Product, and downloads now identify themselves with a standardUser-Agent, contributed by Erik Thauvin. - Fixed #57: incorrect HTML encoding for bean values with Unicode. HTML encoding and decoding handle surrogate code points, and numeric entities with invalid code points are left as-is.
- Added support for Java 26. RIFE2 runs on Java 17 and later, and is tested on Java 17, 21, 25 and 26.
- Added a snapshot publishing workflow, so that snapshot builds are available between releases.
- Improvements to tests and CI.
- Updated to latest
bld, which is now 3.0.0. - Updated to latest dependencies. The embedded servers moved to Jetty 12.1.12 and Tomcat 11.0.25.
Compatibility notes
Authentication
- The authentication and remember-me cookies now carry
SameSite=Laxby default, where they previously carried noSameSiteattribute at all. An application that needs these cookies delivered cross-site can setAuthConfig.cookieSameSite(SameSite.NONE), but browsers only accept that when the cookie isSecureas well, and RIFE2 setsSecurefrom the scheme of the request, so the request has to be recognized as HTTPS.
Transactions and control flow
- Transaction completion changed. An exception that implements
ControlFlowRuntimeExceptionnow commits the enclosing transaction whether it is aRuntimeExceptionor anError. Previously only theRuntimeExceptionones committed and everyErrorrolled back, which is what the web engine's own control flow is built on, sinceredirect,respond,deferandnextthrow subclasses ofLightweightError. ALightweightErrorthat does not implementControlFlowRuntimeExceptionstill rolls back. A commit that fails now replaces the exception that ended the transaction and keeps the original one as a suppressed exception. - The continuation failure exceptions
ContinuationsNotActiveException,CallTargetNotFoundException,MissingActiveContinuationConfigRuntimeExceptionandNoContinuableInstanceAvailableExceptionno longer implementControlFlowRuntimeException. An element transaction that ends with one of them now rolls back where it previously committed, and acatchblock inside a continuable method now sees them where instrumentation previously rethrew them past it. The continuation signalsPauseException,CallException,AnswerExceptionandStepBackExceptionare unchanged.
Database
GenericQueryManagergained five methods that take aDbPreparedStatementHandler, so a class that implements the interface directly has to implement those as well.
Bean population and conversion
Optionalbean properties are now unwrapped transparently, so an application that already had anOptionalgetter renders and persists the contained value where it previously wroteOptional[value].- Populating a bean from parameters now falls back to
Convert.toTypefor property types it has no specific branch for. Properties that were previously left unset are now filled in, and a value that will not convert records a validation error, so a form that used to validate can now fail. - Text read through a constraint's
formatmust be consumed whole and must fit the property. A whole number property that used to truncate a fraction, wrap an out of range value around, or keep only the part of the text the format could read now refuses the value: aValidatedbean records a validation error, and a plain bean throws aBeanUtilsException. AcharorCharacterproperty that used to keep the first character of longer text now refuses it too, recording a validation error on aValidatedbean and leaving the property unset otherwise. java.sql.Timeproperties are formatted with the time format, so their default rendering changes fromyyyyMMddHHmmssSSSZtoHHmmssSSSZ. Thejava.sqldate and time types are also recognized beforejava.util.Datewhen populating a bean, where they previously landed in theDatebranch and were rejected.- Bean property names are uppercased with
Locale.ROOT, so case-insensitive matching of parameter names to bean properties no longer depends on the JVM's default locale. editable(false)now also stops uploaded files from being assigned, matching what it already did for regular parameter values.Convert.toDate,toInstant,toLocalDate,toLocalDateTimeandtoLocalTimefall back to the JDK's ISO-8601 parser when none of the configured formats match, instead of throwing aConversionException.toDateandtoInstantneed the full ISO instant form with a zone offset. Thejava.sqlconversions are unchanged.
Forms and validation
- Generated form field values, including a constraint's
defaultValue, are written the way the field submits them back. A constraint'sformatis no longer applied toString,charandCharacter,booleanandBoolean,StringBuffer,StringBuilder, enums, or arrays of those. A default now only stands in when the property holds no value at all, rather than when its first value is missing. ConstrainedProperty.clone()copies constraint values throughObjectUtils.deepClone, so formats,inListarrays and the copyable associations are no longer shared with the original and mutating them through a clone no longer reaches it. An immutable value such as aStringdefault value is shared rather than copied, and a custom object is copied by its ownclone()implementation.ValidityChecks.checkNotEmptyaccepts anyCharSequenceinstead of throwing aClassCastExceptionon aStringBuilderorStringBuffer, andcheckInListignores nulls both in the allowed values and in the value being checked, instead of throwing aNullPointerException.Validation.hasPropertyConstraintactivates lazy validation like the other accessors do. Relational handling in the generic query manager could previously be skipped on a subclass that registers its constraints inactivateValidation().
Web engine
Site.destroy()moved toRouter.destroy(), so adestroy()on a grouped router that never ran before now runs. Routers are also destroyed before the schedulers stop and the datasources close, which is the reverse of the previous order.route:inputs:tags emit an extra hidden CSRF input when a token is active for the request, so tests that compare generated HTML exactly will need updating.- Responses with content type
text/event-streamare never gzip compressed, regardless ofRifeConfig.engine().getGzipCompressionTypes(). - Multipart requests take their query string parameters from RIFE2 rather than from the container, decoded as UTF-8. On a container configured with a different URI encoding, those parameters decode differently.
- HTML encoding and decoding treat a surrogate pair as one code point, so generated HTML changes for content outside the basic multilingual plane.
Workflow
Workflow.waitForPausedWorkreturnsbooleaninstead ofvoid,truewhen work is paused andfalsewhen no active work remains. Existing callers can keep ignoring the result but have to be recompiled, and a subclass that overrides the method has to change its return type.- Event listeners are notified before the work they awaken resumes, which is the reverse of the previous order.
Out-of-container testing
- Mock cookies without a path are stored under
/inMockConversation, so they no longer sit beside a cookie that specifies/.
