User's Guide
The complete guide to building web applications with the Oorian framework. From core concepts to production deployment, this guide covers everything you need to know.
Table of Contents
1. Introduction
What is Oorian?
Oorian is a server-side Java web framework that lets you build interactive web applications entirely in Java. There is no JavaScript to write, no HTML templates to maintain, and no frontend build tools to configure. You write Java, and Oorian handles everything else.
With Oorian, HTML elements are Java objects. A <div> is a Div,
a <button> is a Button, and CSS properties are set through
type-safe methods like setDisplay(Display.FLEX). Your IDE's autocomplete,
refactoring, and debugging tools work naturally with every part of your UI.
Each page can be independently configured for AJAX, Server-Sent Events (SSE), or WebSocket communication. A simple contact form might use AJAX, while a real-time dashboard uses SSE, and a collaborative editor uses WebSocket — all in the same application.
The Wrapper Approach
Most server-side Java frameworks try to build their own UI components from scratch. They end up with mediocre grids, basic charts, and limited editors that can't compete with the specialized JavaScript libraries the rest of the industry uses.
Oorian takes a different approach: wrap the best, don't reinvent the rest. Need a data grid? Use AG Grid, the industry standard. Need charts? Use ECharts or Chart.js. Need a rich text editor? Use CKEditor, TinyMCE, or Quill. Each of these libraries is maintained by a dedicated team that does nothing but perfect that one component.
Oorian provides Java wrapper libraries for over 50 JavaScript UI libraries. Every wrapper follows the same conventions for configuration, events, and data binding. You learn the Oorian way once, and every library feels familiar.
No Vendor Lock-In
With frameworks like Vaadin or ZK, you're locked into their proprietary component library. If their grid doesn't meet your needs, you're stuck. If they discontinue a component, you have to rewrite your code.
Oorian gives you freedom. Start with one charting library, switch to another later. Use different grid libraries for different use cases in the same application. Your code stays clean because every wrapper follows the same patterns, and switching libraries is a matter of changing imports, not rewriting your application.
Production Proven
Oorian is not a theoretical framework or a weekend project. It has powered iGradePlus, a commercial SaaS application, in production for over 10 years. iGradePlus serves schools and institutions with hundreds of interactive pages — all built with Oorian, all in pure Java, with zero custom JavaScript.
This decade of real-world use has shaped every design decision in the framework. Features exist because they solved real problems. APIs are designed because they proved themselves in production. Oorian is built by practitioners, for practitioners.
Who is Oorian For?
Oorian is designed for Java developers who want to:
- Build web applications without learning JavaScript frameworks — If you know Java, you know enough to build with Oorian
- Use enterprise-grade UI components — Access the best JavaScript libraries through clean Java APIs
- Leverage their existing Java skills — Object-oriented design, inheritance, polymorphism, and standard debugging all apply directly
- Avoid frontend build complexity — No webpack, no npm, no node_modules, no transpilers
- Choose real-time communication per page — AJAX, SSE, or WebSocket, configured with a single line of code
How This Guide is Organized
This guide is structured to take you from the basics through to advanced topics:
- Sections 2–6 cover the fundamentals: project setup, pages, HTML elements, and CSS styling
- Sections 7–9 cover communication: how Oorian talks to the browser, handles events, and sends commands
- Sections 10–11 cover UI building: forms, validation, and layout components
- Sections 12–14 cover data: sessions, JSON, and XML
- Sections 15–18 cover advanced topics: worker threads, templates, standalone HTML generation, and the embedded server
- Sections 19–21 cover integration, accessibility, and security
If you're just getting started, we recommend the Quick Start Guide first. It walks you through creating your first Oorian application in minutes. Then come back here for the full picture.
2. Getting Started
Prerequisites
Before building with Oorian, ensure you have the following:
- Java 17 or later — Oorian requires JDK 17+
- A servlet container — Apache Tomcat 10+, Jetty 11+, or use Oorian's built-in LaunchPad embedded server
- An IDE — Any Java IDE works. NetBeans, IntelliJ IDEA, and Eclipse all provide full autocomplete and debugging support
Project Structure
An Oorian web application follows the standard Java web application structure:
YourApp/
├── src/
│ └── com/yourcompany/
│ ├── MyApplication.java # Application entry point
│ └── pages/
│ ├── HomePage.java # @Page("/")
│ └── AboutPage.java # @Page("/about")
├── web/
│ ├── WEB-INF/
│ │ └── web.xml
│ └── index.jsp # Redirect to /
└── build.xml
The src/ directory contains your Java source code, organized by package. The
web/ directory contains static resources and the deployment descriptor. Oorian
discovers your pages automatically through package scanning — no additional configuration
files are needed.
The Application Class
Every Oorian application starts with an Application subclass annotated with
@WebListener. This class initializes Oorian and registers the packages
that contain your pages:
@WebListener
public class MyApplication extends Application
{
@Override
public void initialize(ServletContext context)
{
registerPackage("com.yourcompany");
}
@Override
public void destroy(ServletContext context)
{
// Clean up resources
}
}
The registerPackage() call tells Oorian to scan that package and all
sub-packages for classes annotated with @Page. You can register
multiple packages if your application is spread across several.
Your First Page
Create a page by extending HtmlPage and annotating it with
@Page:
@Page("/")
public class HomePage extends HtmlPage
{
@Override
protected void createHead(Head head)
{
setTitle("My First Oorian App");
}
@Override
protected void createBody(Body body)
{
H1 heading = new H1();
heading.setText("Hello, Oorian!");
body.addElement(heading);
P paragraph = new P();
paragraph.setText("Welcome to your first Oorian application.");
body.addElement(paragraph);
}
}
That's it. No HTML templates, no JavaScript, no build tools. Deploy to your servlet
container and visit http://localhost:8080/ to see your page.
For a more detailed walkthrough of creating your first application, including IDE setup and deployment, see the Quick Start Guide.
3. Application Lifecycle
The Application class is the entry point for every Oorian application.
It manages initialization, page discovery, and application-wide configuration.
Initialization
When the servlet container starts, it calls your Application's initialize()
method. This is where you configure your application:
@WebListener
public class MyApplication extends Application
{
@Override
public void initialize(ServletContext context)
{
// Register packages for page discovery
registerPackage("com.myapp");
registerPackage("com.myapp.admin");
// Set application-wide communication mode
setDefaultCommunicationMode(CommunicationMode.WEBSOCKET);
// Enable CSRF protection
setCsrfProtectionEnabled(true);
// Configure upload directory
setTempFilePath("/tmp/myapp/uploads");
}
@Override
public void destroy(ServletContext context)
{
// Release database connections, close resources
}
}
Page Discovery
When you call registerPackage(), Oorian scans all classes in that package
and its sub-packages for the @Page annotation. Each annotated class is
registered as a route. For example, @Page("/dashboard") maps to
http://yoursite.com/dashboard.
You can retrieve information about all registered pages at runtime:
List<PageInfo> pages = Application.getPages();
for (PageInfo page : pages)
{
System.out.println(page.getPath() + " -> " + page.getPageClass().getName());
}
Application-Wide Configuration
The Application class provides several configuration options that apply to all pages:
| Method | Description |
|---|---|
setDefaultCommunicationMode(mode) |
Sets the default communication mode for all pages (AJAX_ONLY, AJAX_WITH_SSE, or WEBSOCKET) |
setDefaultPollInterval(ms) |
Sets the polling interval for AJAX_ONLY mode (in milliseconds) |
setCsrfProtectionEnabled(boolean) |
Enables or disables CSRF token validation application-wide |
setTempFilePath(path) |
Sets the directory for temporary file uploads |
The default communication mode is WEBSOCKET. Individual pages can
override this with setCommunicationMode(). See
Section 7: Communication Modes for details.
4. Pages and Routing
Pages are the fundamental building blocks of an Oorian application. Each page is a Java class
that generates a complete HTML document, and the @Page annotation maps it to a URL.
The @Page Annotation
The @Page annotation defines the URL path for your page. Oorian supports
static paths, dynamic parameters, and multiple parameters in a single path:
// Static path
@Page("/about")
public class AboutPage extends HtmlPage { ... }
// Dynamic parameter
@Page("/users/{id}")
public class UserProfilePage extends HtmlPage { ... }
// Multiple parameters
@Page("/blog/{year}/{slug}")
public class BlogPostPage extends HtmlPage { ... }
// Nested path
@Page("/admin/settings")
public class AdminSettingsPage extends HtmlPage { ... }
Dynamic parameters are enclosed in curly braces ({id}, {slug})
and can be retrieved in your page code using the Parameters object.
Page Structure
Every Oorian page extends HtmlPage and overrides two methods to build
the HTML document:
@Page("/products")
public class ProductsPage extends HtmlPage
{
@Override
protected void createHead(Head head)
{
// Configure the <head> element
setTitle("Our Products");
setDescription("Browse our product catalog.");
head.addCssLink("/css/products.css");
}
@Override
protected void createBody(Body body)
{
// Build the page content
H1 title = new H1();
title.setText("Products");
body.addElement(title);
// Add product cards, tables, etc.
}
}
Page Lifecycle
When a user requests a page, Oorian processes it through a defined lifecycle:
- initializePage() — Called first. Perform authentication checks, load data,
or redirect. Return
falseto stop page rendering. - createHead() — Build the
<head>section: title, meta tags, stylesheets, and scripts. - createBody() — Build the
<body>section: your page content. - Page renders — Oorian serializes the element tree to HTML and sends it to the browser.
The initializePage() method is optional but powerful. Use it for authentication
checks, data loading, or conditional redirects:
@Page("/dashboard")
public class DashboardPage extends HtmlPage
{
@Override
protected boolean initializePage(HttpServletRequest request, HttpServletResponse response)
{
User user = (User) getSession().getAttribute("user");
if (user == null)
{
navigateTo("/login");
return false; // Stop rendering
}
return true; // Continue to createHead/createBody
}
@Override
protected void createHead(Head head) { ... }
@Override
protected void createBody(Body body) { ... }
}
Updating the UI
In SSE and WebSocket communication modes, changes you make to elements are not automatically
sent to the browser. Call sendUpdate() to push all pending changes as a single
batched response:
@Override
public void onEvent(MouseClickedEvent event)
{
statusLabel.setText("Processing...");
progressBar.setWidth("50%");
submitBtn.setDisabled(true);
sendUpdate();
}
In AJAX mode, updates are sent automatically at the end of each request-response cycle.
In SSE and WebSocket modes, you must call sendUpdate() explicitly to push
changes to the browser. It is safe to call in AJAX mode — it simply has no effect.
URL Parameters
Access URL path parameters and query string parameters through the Parameters object.
For dynamic paths like /users/{id}, the parameter name matches the placeholder:
@Page("/users/{id}")
public class UserProfilePage extends HtmlPage
{
@Override
protected void createBody(Body body)
{
Parameters params = getParameters();
// Path parameter: /users/42 -> "42"
int userId = params.getParameterValueAsInt("id");
// Query parameter: /users/42?tab=settings -> "settings"
String tab = params.getParameterValue("tab");
// Check if a parameter exists
boolean hasTab = params.containsParameter("tab");
}
}
The Parameters class provides typed accessor methods for common data types:
| Method | Return Type | Description |
|---|---|---|
getParameterValue(name) |
String |
Returns the raw string value |
getParameterValues(name) |
String[] |
Returns all values for multi-valued parameters |
getParameterValueAsInt(name) |
int |
Parses the value as an integer |
getParameterValueAsDouble(name) |
double |
Parses the value as a double |
getParameterValueAsBoolean(name) |
boolean |
Parses the value as a boolean |
containsParameter(name) |
boolean |
Checks if the parameter exists |
Navigation
Oorian provides several methods for navigating between pages:
// Navigate to another page
navigateTo("/dashboard");
// Navigate with parameters
navigateTo("/users/42?tab=settings");
// Create a URL for use in links
String url = createUrl("/products");
// Go back to the previous page
navigateBack();
// Open a URL in a new browser tab
openInNewWindow("/reports/annual");
When you call navigateTo() inside initializePage(), remember to
return false to prevent the current page from rendering. In event handlers,
navigateTo() triggers the navigation after the current handler completes.
Cache Control
Oorian's CacheControl class provides a fluent builder for constructing HTTP
Cache-Control headers. Use it to control how browsers and CDNs cache your
application's responses.
Static Factory Methods
For common caching strategies, use the built-in factory methods:
// Long-lived static resources (CSS, JS, images with versioned URLs)
// Produces: "public, max-age=31536000, immutable"
CacheControl cache = CacheControl.staticResources();
// Prevent caching entirely (sensitive data)
// Produces: "no-store"
CacheControl cache = CacheControl.preventCaching();
// Allow caching but require revalidation before each use
// Produces: "no-cache"
CacheControl cache = CacheControl.revalidate();
Custom Cache Policies
Build custom policies with the fluent API. All methods return this for
method chaining:
// Public cache with 1-hour max age, must revalidate when stale
CacheControl cache = new CacheControl()
.setPublic()
.setMaxAge(3600)
.mustRevalidate();
// Produces: "public, max-age=3600, must-revalidate"
// Private cache for user-specific content
CacheControl cache = new CacheControl()
.setPrivate()
.setMaxAge(300)
.noTransform();
// CDN-friendly: short browser cache, longer CDN cache
CacheControl cache = new CacheControl()
.setPublic()
.setMaxAge(60)
.setSMaxAge(3600)
.setStaleWhileRevalidate(86400);
The available directives:
| Method | Directive | Description |
|---|---|---|
setPublic() |
public |
Response may be cached by any cache, including shared caches (CDNs, proxies) |
setPrivate() |
private |
Response is for a single user; must not be stored by shared caches |
noCache() |
no-cache |
Caches must revalidate with origin server before each use |
noStore() |
no-store |
No cache should store the response (strongest directive) |
noTransform() |
no-transform |
Proxies must not modify the response body or headers |
mustRevalidate() |
must-revalidate |
Must not use stale responses without revalidation |
proxyRevalidate() |
proxy-revalidate |
Like must-revalidate but only for shared caches |
immutable() |
immutable |
Response body will not change; prevents conditional revalidation |
setMaxAge(int) |
max-age |
Maximum seconds before response is stale |
setSMaxAge(int) |
s-maxage |
Maximum age for shared caches, overrides max-age |
setStaleWhileRevalidate(int) |
stale-while-revalidate |
Serve stale while revalidating in background |
setStaleIfError(int) |
stale-if-error |
Serve stale if origin returns a 5xx error |
Using CacheControl with CssFile
CssFile subclasses have built-in cache control support. Cached-mode CssFiles
(created with a name) default to long-lived caching, while dynamic-mode CssFiles default to
no-cache. You can override the default with setCacheControl():
@Page("/css/app.css")
public class AppStyles extends CssFile
{
public AppStyles()
{
super("app-styles"); // Cached mode: defaults to public, max-age=31536000, immutable
// Override with a custom cache policy if needed
setCacheControl(new CacheControl().setPublic().setMaxAge(86400).mustRevalidate());
}
@Override
protected CssStyleSheet createStyleSheet()
{
// ...
}
}
Setting Cache Headers on Pages
For page responses, set the Cache-Control header directly on the
HttpServletResponse in initializePage():
@Override
protected boolean initializePage(
HttpServletRequest request, HttpServletResponse response)
{
// Prevent caching of sensitive pages
response.setHeader("Cache-Control",
CacheControl.preventCaching().toString());
return true;
}
Cached-mode CssFile instances (those created with a name in the constructor)
automatically use public, max-age=31536000, immutable with ETag support.
Dynamic-mode instances (no-arg constructor) default to no-cache. Use
setCacheControl() only when you need to override these defaults.
5. HTML Elements
Oorian provides Java classes for every standard HTML element. Instead of writing HTML markup, you construct a tree of Java objects that Oorian renders to HTML.
Element Hierarchy
All Oorian elements inherit from a common base:
- Element — The abstract base class for all HTML elements. Provides methods for setting attributes, adding child elements, and inline styling.
- StyledElement — Extends Element with CSS class management methods
(
addClass(),removeClass()). Use this as your base when building custom components that need dynamic CSS classes.
You never instantiate Element directly. Instead, use the concrete subclasses
like Div, Span, Button, and so on.
Common Elements
| Class | HTML Tag | Description |
|---|---|---|
Div |
<div> |
Generic block container |
Span |
<span> |
Generic inline container |
P |
<p> |
Paragraph |
H1 – H6 |
<h1> – <h6> |
Heading levels 1 through 6 |
Anchor |
<a> |
Hyperlink |
Button |
<button> |
Clickable button |
Image |
<img> |
Image |
Table |
<table> |
Data table |
Ul |
<ul> |
Unordered list |
Ol |
<ol> |
Ordered list |
Li |
<li> |
List item |
Section |
<section> |
Thematic section |
Nav |
<nav> |
Navigation section |
Header |
<header> |
Header section |
Footer |
<footer> |
Footer section |
Creating and Configuring Elements
Create an element by instantiating its class, configure it with setter methods, and add it to a parent:
// Create elements
Div container = new Div();
H2 title = new H2();
P description = new P();
// Configure text content
title.setText("Welcome");
description.setText("Thanks for visiting our site.");
// Configure styling
container.setMaxWidth("800px");
container.setMargin("0 auto");
container.setPadding("40px");
// Build the tree
container.addElement(title);
container.addElement(description);
body.addElement(container);
Attributes
Set standard and custom HTML attributes using addAttribute() and
setId():
Div card = new Div();
card.setId("main-card");
card.addAttribute("role", "article");
card.addAttribute("data-category", "featured");
Anchor link = new Anchor();
link.setHref("/about");
link.setText("About Us");
link.addAttribute("target", "_blank");
Image logo = new Image();
logo.setSrc("/images/logo.png");
logo.setAlt("Company Logo");
logo.setWidth("200px");
Input Elements
Oorian provides type-specific subclasses for each HTML input type:
| Class | Input Type | Description |
|---|---|---|
TextInput |
text |
Single-line text field |
PasswordInput |
password |
Password field (masked input) |
EmailInput |
email |
Email address field |
Checkbox |
checkbox |
Checkbox toggle |
RadioButton |
radio |
Radio button (one of many) |
Select |
<select> |
Dropdown selection |
TextArea |
<textarea> |
Multi-line text field |
FileInput |
file |
File upload field |
HiddenInput |
hidden |
Hidden form value |
// Text input with placeholder
TextInput nameField = new TextInput();
nameField.setName("fullName");
nameField.setPlaceholder("Enter your name");
// Select dropdown
Select countrySelect = new Select();
countrySelect.setName("country");
countrySelect.addOption(new Option("us", "United States"));
countrySelect.addOption(new Option("ca", "Canada"));
countrySelect.addOption(new Option("uk", "United Kingdom"));
// Checkbox
Checkbox agreeBox = new Checkbox();
agreeBox.setName("agree");
Child Element Management
Oorian provides methods for managing an element's children:
Div container = new Div();
// Add child elements
container.addElement(header);
container.addElement(content);
container.addElement(footer);
// Remove a specific child
container.removeElement(content);
// Remove all children
container.removeAllElements();
// Check for children
boolean hasChildren = container.hasElements();
int count = container.getElementCount();
// Insert raw HTML content
RawHtml html = new RawHtml("<em>Emphasized text</em>");
container.addElement(html);
Always use the concrete subclass for the HTML element you need. Write
new Div(), not new Element("div"). The only exception
is SVG elements, which have no dedicated subclass.
6. CSS Styling
Oorian provides multiple ways to apply CSS to your elements: inline style methods, CSS class management, and programmatic stylesheets. Each approach serves a different purpose, and you'll typically use a combination of all three.
Inline Style Methods
Every element has type-safe methods for the most common CSS properties. These
methods translate directly to inline style attributes on the rendered HTML:
Div card = new Div();
// Layout
card.setDisplay(Display.FLEX);
card.setFlexDirection(FlexDirection.COLUMN);
card.setAlignItems(AlignItems.CENTER);
card.setGap("16px");
// Sizing
card.setWidth("300px");
card.setMaxWidth("100%");
card.setPadding("24px");
card.setMargin("0 auto");
// Appearance
card.setBackgroundColor("#ffffff");
card.setColor("#1f2937");
card.setBorderRadius("12px");
card.setBoxShadow("0 2px 8px rgba(0, 0, 0, 0.1)");
// Position
card.setPosition(Position.RELATIVE);
card.setOverflow(Overflow.HIDDEN);
For CSS properties that don't have a dedicated method, use addStyleAttribute():
// For properties without a dedicated method
card.addStyleAttribute("grid-template-columns", "repeat(3, 1fr)");
card.addStyleAttribute("backdrop-filter", "blur(10px)");
card.addStyleAttribute("transition", "all 0.3s ease");
CSS Class Management
Elements that extend StyledElement can dynamically add and remove
CSS classes:
Div alert = new Div();
// Add one or more classes (space-separated)
alert.addClass("alert alert-success");
// Remove a class
alert.removeClass("alert-success");
// Add a different class
alert.addClass("alert-error");
This is particularly useful in event handlers where you need to change an element's appearance in response to user actions.
Programmatic Stylesheets
For page-level styles, use CssStyleSheet and CssRule to
build CSS programmatically. This gives you the same type-safe methods available on
elements, but applied to CSS selectors:
CssStyleSheet css = new CssStyleSheet();
// Base card style
CssRule cardRule = new CssRule(".card");
cardRule.setBackgroundColor("#ffffff");
cardRule.setPadding("24px");
cardRule.setBorderRadius("12px");
cardRule.setBoxShadow("0 2px 8px rgba(0, 0, 0, 0.1)");
css.addRule(cardRule);
// Hover state
CssRule cardHover = new CssRule(".card:hover");
cardHover.setBoxShadow("0 4px 16px rgba(0, 0, 0, 0.15)");
css.addRule(cardHover);
// Add to page
Style style = new Style();
style.setText(css.toString());
head.addElement(style);
Responsive Design with Media Queries
Use CssMediaQuery to add responsive styles:
CssStyleSheet css = new CssStyleSheet();
// Default: 3-column grid
CssRule grid = new CssRule(".product-grid");
grid.setDisplay(Display.GRID);
grid.addStyleAttribute("grid-template-columns", "repeat(3, 1fr)");
grid.setGap("24px");
css.addRule(grid);
// Tablet: 2-column grid
CssMediaQuery tablet = new CssMediaQuery("(max-width: 1024px)");
CssRule tabletGrid = new CssRule(".product-grid");
tabletGrid.addStyleAttribute("grid-template-columns", "repeat(2, 1fr)");
tablet.addRule(tabletGrid);
css.addMediaQuery(tablet);
// Mobile: 1-column
CssMediaQuery mobile = new CssMediaQuery("(max-width: 768px)");
CssRule mobileGrid = new CssRule(".product-grid");
mobileGrid.addStyleAttribute("grid-template-columns", "1fr");
mobile.addRule(mobileGrid);
css.addMediaQuery(mobile);
Reusable Stylesheets with CssFile
For styles shared across multiple pages, extend CssFile to serve CSS
as a proper file with browser caching support:
@Page("/css/common.css")
public class CommonStyles extends CssFile
{
public static final String PATH = "/css/common.css";
public CommonStyles()
{
super("common-styles"); // Cache key
}
@Override
protected CssStyleSheet createStyleSheet()
{
CssStyleSheet css = new CssStyleSheet();
CssRule body = new CssRule("body");
body.setFontFamily("'Inter', sans-serif");
body.setColor("#1f2937");
body.setMargin("0");
css.addRule(body);
// Add more shared rules...
return css;
}
public static void addStyleSheet(Head head)
{
head.addCssLink(PATH);
}
}
Then reference it from any page:
@Override
protected void createHead(Head head)
{
CommonStyles.addStyleSheet(head); // Links to /css/common.css
}
Always use dedicated methods like setDisplay(), setPosition(),
and setBackgroundColor() when available. Only fall back to
addStyleAttribute() for CSS properties that don't have a dedicated method.
Built-in methods provide type safety through enums and prevent property name typos.
7. Communication Modes
One of Oorian's most distinctive features is its flexible communication model. Each page in your application can independently choose how it communicates with the browser. A simple form page can use basic AJAX, a dashboard can use Server-Sent Events for live updates, and a collaborative editor can use WebSocket for real-time bidirectional communication.
The Three Modes
| Mode | Direction | Best For |
|---|---|---|
AJAX_ONLY |
Client → Server (request/response) | Simple forms, static content, pages with minimal interactivity |
AJAX_WITH_SSE |
Client → Server (AJAX) + Server → Client (SSE) | Dashboards, status monitors, live feeds, progress indicators |
WEBSOCKET |
Bidirectional | Real-time collaboration, chat, interactive applications |
AJAX_ONLY
AJAX_ONLY is the simplest communication mode. The browser sends AJAX requests
to the server when events occur (button clicks, form submissions, etc.), and the server
responds with updates. The server cannot push updates to the browser on its own.
This mode is ideal for pages where all interaction is user-initiated: contact forms, login pages, settings panels, and pages that don't need live updates.
@Page("/contact")
public class ContactPage extends HtmlPage
{
public ContactPage()
{
setCommunicationMode(CommunicationMode.AJAX_ONLY);
}
}
AJAX_WITH_SSE
AJAX_WITH_SSE combines AJAX for client-to-server communication with
Server-Sent Events (SSE) for server-to-client push. The browser still sends events via
AJAX, but the server can push updates at any time through an SSE channel.
This mode is perfect for dashboards and monitoring pages where data changes on the server and needs to be reflected in the browser without the user taking action. It's more efficient than WebSocket for one-way server push because SSE uses a simple HTTP connection.
@Page("/dashboard")
public class DashboardPage extends HtmlPage
{
public DashboardPage()
{
setCommunicationMode(CommunicationMode.AJAX_WITH_SSE);
}
}
WEBSOCKET
WEBSOCKET provides full bidirectional communication through a persistent
connection. Both the client and server can send messages at any time with minimal
latency. This is the default mode and the most powerful option.
WebSocket is ideal for highly interactive applications, real-time collaboration, chat interfaces, and any page where low-latency bidirectional communication matters. The persistent connection eliminates the overhead of repeated HTTP requests.
@Page("/editor")
public class EditorPage extends HtmlPage
{
public EditorPage()
{
// WEBSOCKET is the default, but you can set it explicitly
setCommunicationMode(CommunicationMode.WEBSOCKET);
}
}
Configuring Per Page
Set the communication mode in your page's constructor using
setCommunicationMode(). Each page operates independently, so you can mix
modes freely within the same application:
// Simple form - AJAX is sufficient
@Page("/settings")
public class SettingsPage extends HtmlPage
{
public SettingsPage()
{
setCommunicationMode(CommunicationMode.AJAX_ONLY);
}
}
// Live dashboard - needs server push
@Page("/metrics")
public class MetricsPage extends HtmlPage
{
public MetricsPage()
{
setCommunicationMode(CommunicationMode.AJAX_WITH_SSE);
}
}
// Collaborative workspace - full bidirectional
@Page("/workspace")
public class WorkspacePage extends HtmlPage
{
public WorkspacePage()
{
setCommunicationMode(CommunicationMode.WEBSOCKET);
}
}
You can also set a default for all pages in your Application class, and then override it on specific pages that need a different mode:
@WebListener
public class MyApplication extends Application
{
@Override
public void initialize(ServletContext context)
{
registerPackage("com.myapp");
// Default all pages to AJAX_WITH_SSE
setDefaultCommunicationMode(CommunicationMode.AJAX_WITH_SSE);
}
}
If you don't set a communication mode, Oorian defaults to WEBSOCKET.
This is the most capable mode and works well for most interactive applications. Only
switch to AJAX_ONLY or AJAX_WITH_SSE when you have a
specific reason, such as simplifying infrastructure (no WebSocket proxy needed) or
reducing resource usage on pages that don't need bidirectional communication.
Pushing Updates with sendUpdate()
When you modify elements in an event handler — changing text, toggling visibility, updating styles — the changes need to reach the browser. How this works depends on the communication mode:
| Mode | Behavior |
|---|---|
AJAX_ONLY |
Updates are sent automatically at the end of the request-response cycle. No explicit call needed. |
AJAX_WITH_SSE |
AJAX responses are automatic. For server-initiated pushes (e.g., from a worker thread), call sendUpdate(). |
WEBSOCKET |
You must call sendUpdate() to push changes to the browser. |
The sendUpdate() method batches all pending element changes into a single
message, minimizing network round-trips:
@Override
public void onEvent(MouseClickedEvent event)
{
statusLabel.setText("Processing...");
progressBar.setWidth("50%");
submitBtn.setDisabled(true);
sendUpdate(); // Push all three changes in one batch
}
It is safe to call sendUpdate() in any communication mode. In
AJAX_ONLY mode it simply has no effect, so you can include it
unconditionally if your page might later switch modes.
8. Event Handling
Oorian uses a JDK-style event model that will feel immediately familiar if you have experience with Swing, JavaFX, or SWT. Elements fire events, and listeners handle them. There are no custom annotations, no magic strings, and no reflection tricks — just standard Java interfaces and method calls.
Event Types and Listeners
Every event type has a corresponding listener interface. Your page class implements the listener interface and registers itself (or another object) to receive events from specific elements.
| Event | Listener Interface | Triggered By |
|---|---|---|
MouseClickedEvent |
MouseClickListener |
Single click on an element |
MouseDblClickedEvent |
MouseClickListener |
Double click on an element |
KeyDownEvent |
KeyListener |
Key press while element has focus |
InputChangeEvent |
InputListener |
Input value changes (fires on each keystroke) |
InputCompleteEvent |
InputListener |
Input loses focus after value has changed |
FormEvent |
FormListener |
Form submission |
FocusInEvent / FocusOutEvent |
FocusListener |
Element gains or loses focus |
ScrollEvent |
ScrollListener |
Scroll position changes |
DragDropEvent |
DragDropListener |
Drag and drop interaction |
All events and listeners are in the com.oorian.messaging.events.client package.
Registering Listeners
To handle events, your page implements the appropriate listener interface and registers itself on the elements it wants to listen to. Here is a complete example of a counter page that handles click events from two buttons:
@Page("/counter")
public class CounterPage extends HtmlPage implements MouseClickListener
{
private int count = 0;
private Span countLabel;
private Button incrementBtn;
private Button resetBtn;
@Override
protected void createBody(Body body)
{
countLabel = new Span();
countLabel.setText("0");
body.addElement(countLabel);
incrementBtn = new Button("Increment");
incrementBtn.registerListener(this, MouseClickedEvent.class);
body.addElement(incrementBtn);
resetBtn = new Button("Reset");
resetBtn.registerListener(this, MouseClickedEvent.class);
body.addElement(resetBtn);
}
@Override
public void onEvent(MouseClickedEvent event)
{
if (event.getSource() == incrementBtn)
{
count++;
}
else if (event.getSource() == resetBtn)
{
count = 0;
}
countLabel.setText(String.valueOf(count));
sendUpdate();
}
}
The pattern is straightforward: implement the listener interface, register on each element
with registerListener(this, EventClass.class), and handle events in the
onEvent() method.
Use event.getSource() to determine which element triggered the event.
Compare with == against stored element references to route events to
the correct handler logic. This works because each element is a unique Java object
instance.
The sendUpdate() Pattern
After modifying elements in an event handler, call sendUpdate() to push
the changes to the browser. This is Oorian's core interaction pattern:
- The browser sends an event to the server (click, form submit, input change, etc.)
- Oorian dispatches the event to your listener
- Your handler modifies element properties (text, visibility, styles, etc.)
- You call
sendUpdate()to push all pending changes to the browser
Oorian automatically detects which elements have changed and sends only the minimal updates needed. If you change the text of one label, only that label's update is sent — not the entire page.
Call sendUpdate() once at the end of your event handler, after all
modifications are complete. Oorian batches all changes into a single response, so
calling it multiple times is unnecessary.
9. Session Management
Oorian provides the OorianSession class for managing server-side session
data. It wraps the standard HTTP session with typed accessors and integrates with
Oorian's page lifecycle.
Accessing the Session
From within an HtmlPage, use the inherited getSession()
method. Outside of a page context, use the static OorianSession.get()
method:
// From within an HtmlPage
OorianSession session = getSession();
session.setAttribute("user", currentUser);
// From anywhere in the application (static access)
OorianSession session = OorianSession.get();
User user = (User) session.getAttribute("user");
Do not access HttpSession directly. Always use
OorianSession for session management. It provides thread-safe access,
integrates with Oorian's page caching, and offers typed attribute accessors.
Storing and Retrieving Attributes
OorianSession stores attributes as Object values and
provides typed getters for common types. Each typed getter has an overload that
accepts a default value:
OorianSession session = getSession();
// Store attributes
session.setAttribute("username", "john.doe");
session.setAttribute("userId", 42);
session.setAttribute("isAdmin", true);
session.setAttribute("balance", 1250.75);
// Retrieve with typed accessors
String username = session.getAttributeAsString("username");
Integer userId = session.getAttributeAsInt("userId");
Boolean isAdmin = session.getAttributeAsBoolean("isAdmin");
Double balance = session.getAttributeAsDouble("balance");
// Retrieve with default values (returned if attribute is null)
String theme = session.getAttributeAsString("theme", "light");
Integer pageSize = session.getAttributeAsInt("pageSize", 25);
Boolean darkMode = session.getAttributeAsBoolean("darkMode", false);
// Retrieve as raw Object (for custom types)
User user = (User) session.getAttribute("user");
// Remove an attribute
session.removeAttribute("tempData");
Session Info
OorianSession exposes standard session metadata and lifecycle controls:
| Method | Description |
|---|---|
getId() |
Returns the unique session identifier |
getCreationTime() |
Returns when the session was created (milliseconds since epoch) |
isNew() |
Returns true if the session was just created and the client has not yet acknowledged it |
setMaxInactiveInterval(seconds) |
Sets the maximum idle time before the session expires |
getMaxInactiveInterval() |
Returns the maximum inactive interval in seconds |
invalidate() |
Destroys the session and unbinds all attributes |
OorianSession session = getSession();
// Set session timeout to 30 minutes
session.setMaxInactiveInterval(1800);
// Logout: clear data and destroy the session
session.removeAttribute("user");
session.invalidate();
navigateTo("/login");
Cookies
Oorian provides cookie management through HtmlPage methods. Use cookies
for data that needs to persist across sessions, such as user preferences or
"remember me" tokens:
// Read a cookie
Cookie themeCookie = getCookie("theme");
if (themeCookie != null)
{
applyTheme(themeCookie.getValue());
}
// Read with a default value
Cookie langCookie = getCookie("language", "en");
String language = langCookie.getValue();
// Create and add a cookie
Cookie cookie = addCookie("theme", "dark");
// Create a cookie with custom settings
Cookie rememberMe = new Cookie("remember", "token123");
rememberMe.setMaxAge(60 * 60 * 24 * 30); // 30 days
rememberMe.setPath("/");
rememberMe.setHttpOnly(true);
rememberMe.setSecure(true);
addCookie(rememberMe);
Cookies are stored on the client and can be read or modified by the user. Never
store passwords, session tokens, or other sensitive information in cookies. Use
server-side session attributes for sensitive data, and cookies only for non-sensitive
preferences. Always set HttpOnly and Secure flags on
cookies that carry any kind of token.
10. Forms and Validation
Oorian provides two approaches to form handling: OorianForm for basic
forms with server-side processing, and ValidatedForm for forms that need
built-in validation with automatic error display.
Basic Forms with OorianForm
Use OorianForm with FormListener to handle form submissions.
Give each input a name with setName(), and access submitted values through
the Parameters object:
@Page("/contact")
public class ContactPage extends HtmlPage implements FormListener
{
private TextInput nameInput;
private TextInput emailInput;
private TextArea messageInput;
private Span statusLabel;
@Override
protected void createBody(Body body)
{
OorianForm form = new OorianForm();
form.registerListener(this, FormEvent.class);
nameInput = new TextInput();
nameInput.setName("name");
form.addElement(nameInput);
emailInput = new TextInput();
emailInput.setName("email");
form.addElement(emailInput);
messageInput = new TextArea();
messageInput.setName("message");
form.addElement(messageInput);
form.addElement(new Button("Send Message"));
statusLabel = new Span();
form.addElement(statusLabel);
body.addElement(form);
}
@Override
public void onEvent(FormEvent event)
{
Parameters params = event.getParameters();
String name = params.getParameterValue("name");
String email = params.getParameterValue("email");
String message = params.getParameterValue("message");
// Process the form data
sendContactEmail(name, email, message);
statusLabel.setText("Thank you! Your message has been sent.");
sendUpdate();
}
}
The Parameters Class
The Parameters class provides typed access to form values. All values
arrive as strings from the browser, and Parameters handles type conversion for you:
| Method | Returns |
|---|---|
getParameterValue(name) |
String — the first value, or null |
getParameterValueAsInt(name) |
Integer — parsed integer, or null |
getParameterValueAsLong(name) |
Long — parsed long, or null |
getParameterValueAsDouble(name) |
Double — parsed double, or null |
getParameterValueAsFloat(name) |
Float — parsed float, or null |
getParameterValueAsBoolean(name) |
Boolean — parsed boolean, or false |
getParameterValues(name) |
List<String> — all values (for multi-select, checkboxes) |
containsParameter(name) |
boolean — true if the parameter exists |
Parameters params = event.getParameters();
// String value
String name = params.getParameterValue("name");
// Numeric values with automatic parsing
Integer age = params.getParameterValueAsInt("age");
Double salary = params.getParameterValueAsDouble("salary");
// Boolean (checkbox)
Boolean subscribe = params.getParameterValueAsBoolean("subscribe");
// Multi-value (checkboxes, multi-select)
List<String> selectedRoles = params.getParameterValues("roles");
// Check if parameter was submitted
if (params.containsParameter("agreeToTerms"))
{
// User checked the terms checkbox
}
Validated Forms
ValidatedForm extends OorianForm with built-in validation
support. Each input field is wrapped in a ValidatedInput that defines
validation rules through a fluent API:
// Create a validated form
ValidatedForm form = new ValidatedForm();
form.registerListener(this, FormEvent.class);
// Create input elements
TextInput emailInput = new TextInput();
emailInput.setName("email");
Span emailError = new Span();
PasswordInput passwordInput = new PasswordInput();
passwordInput.setName("password");
Span passwordError = new Span();
// Register validated inputs with rules
form.addValidatedInput(
new ValidatedInput<>(emailInput, emailError)
.required("Email is required")
.email("Please enter a valid email address")
);
form.addValidatedInput(
new ValidatedInput<>(passwordInput, passwordError)
.required("Password is required")
.minLength(8, "Password must be at least 8 characters")
);
// Add elements to the form
form.addElement(emailInput);
form.addElement(emailError);
form.addElement(passwordInput);
form.addElement(passwordError);
form.addElement(new Button("Sign In"));
Each ValidatedInput takes the input element and an error display element
(typically a Span). When validation fails, the error message is automatically
shown in the error display element, and CSS classes are applied for visual feedback.
Built-in Validators
Oorian includes a comprehensive set of validators that cover common form validation scenarios:
| Validator Method | Description |
|---|---|
required(message) |
Field must not be empty |
email(message) |
Must be a valid email address |
minLength(n, message) |
Minimum character length |
maxLength(n, message) |
Maximum character length |
length(min, max, message) |
Character length must be within range |
pattern(regex, message) |
Must match a regular expression pattern |
range(min, max, message) |
Numeric value must be within range |
url(message) |
Must be a valid URL |
phone(format, message) |
Must match a phone number format |
numeric(message) |
Must be a numeric value |
alphanumeric(message) |
Must contain only letters and numbers |
creditCard(message) |
Must be a valid credit card number |
date(format, message) |
Must match a date format |
Validators are chained using the fluent API. They are evaluated in the order they are added, and validation stops at the first failure for each field:
new ValidatedInput<>(usernameInput, usernameError)
.required("Username is required")
.minLength(3, "Username must be at least 3 characters")
.maxLength(20, "Username must be 20 characters or fewer")
.alphanumeric("Username must contain only letters and numbers");
Handling Validation in onEvent
In your FormListener, call form.validate(event) to validate
all fields at once. The returned ValidationResult tells you whether the form
is valid, and error messages are automatically displayed next to each invalid field:
@Override
public void onEvent(FormEvent event)
{
ValidationResult result = form.validate(event);
if (result.isValid())
{
// All fields passed validation - process the form
Parameters params = event.getParameters();
String email = params.getParameterValue("email");
String password = params.getParameterValue("password");
createAccount(email, password);
}
// sendUpdate() pushes validation error messages to the browser
sendUpdate();
}
Cross-Field Validation
For validation rules that compare two or more fields, use CompareValidator
as a form-level validator. This is commonly used for password confirmation and date range
validation:
// Password confirmation
form.addFormValidator(
new CompareValidator("password", "confirmPassword",
CompareValidator.Operation.EQUALS)
.withMessage("Passwords do not match")
);
// Date range validation
form.addFormValidator(
new CompareValidator("startDate", "endDate",
CompareValidator.Operation.LESS_THAN)
.withMessage("Start date must be before end date")
);
The CompareValidator.Operation enum supports: EQUALS,
NOT_EQUALS, LESS_THAN, LESS_THAN_OR_EQUALS,
GREATER_THAN, and GREATER_THAN_OR_EQUALS.
11. Containers and Layout Components
Oorian provides a set of layout components that handle common layout patterns without requiring manual CSS. These components use CSS Flexbox and Grid internally but expose simple, semantic Java APIs.
Container
Container provides centered, width-constrained content. It applies a
maximum width and horizontal auto-margins to keep content from stretching too wide
on large screens:
// Default container (LG - 1024px max-width)
Container container = new Container();
container.addElement(content);
// Small container for narrow content like articles
Container narrow = new Container(Container.Size.SM);
// Extra large container for wide layouts
Container wide = new Container(Container.Size.XL);
// Custom max-width
Container custom = new Container("900px");
Preset sizes follow common responsive breakpoints:
| Size | Max Width |
|---|---|
Container.Size.SM |
640px |
Container.Size.MD |
768px |
Container.Size.LG |
1024px (default) |
Container.Size.XL |
1280px |
Container.Size.XXL |
1536px |
Container.Size.FLUID |
100% (no constraint) |
RegionLayout
RegionLayout provides a five-region layout familiar to Java Swing
developers. It divides the page into north, south, east, west, and center regions.
The center region expands to fill all available space:
+--------------------------------------+
| NORTH |
+--------+-----------------+-----------+
| | | |
| WEST | CENTER | EAST |
| | | |
+--------+-----------------+-----------+
| SOUTH |
+--------------------------------------+
// Classic application layout
RegionLayout layout = new RegionLayout();
layout.north(header)
.west(navigation)
.center(mainContent)
.east(sidebar)
.south(footer);
// Configure region sizes
layout.setWestWidth(280);
layout.setEastWidth(300);
layout.setNorthHeight(64);
layout.setSouthHeight(48);
// Make the layout fill the entire viewport
layout.fillViewport();
body.addElement(layout);
All regions are optional. A simple header-content-footer layout only uses three:
RegionLayout layout = new RegionLayout();
layout.north(header)
.center(content)
.south(footer);
HStack and VStack
HStack arranges children horizontally (left to right), and
VStack arranges children vertically (top to bottom). They use CSS Flexbox
internally and provide simple controls for spacing and alignment:
// Horizontal button group with 8px gap
HStack buttonGroup = new HStack(8);
buttonGroup.addElement(new Button("Save"));
buttonGroup.addElement(new Button("Cancel"));
// Vertical form layout with 16px spacing
VStack formLayout = new VStack(16);
formLayout.addElement(nameField);
formLayout.addElement(emailField);
formLayout.addElement(buttonGroup);
// Navigation bar with space between items
HStack navbar = new HStack();
navbar.spaceBetween();
navbar.addElement(logo);
navbar.addElement(menuItems);
navbar.addElement(userProfile);
ComposableLayout
ComposableLayout is a slot-based CSS Grid layout for custom arrangements.
You define named slots and assign content to them, then control positioning using
CSS Grid template syntax:
// Custom dashboard layout with named slots
ComposableLayout layout = new ComposableLayout();
layout.setGridTemplate(
"'header header' auto " +
"'sidebar main' 1fr " +
"'footer footer' auto / 250px 1fr"
);
// Assign content to named slots
layout.slot("header", headerContent);
layout.slot("sidebar", sidebarContent);
layout.slot("main", mainContent);
layout.slot("footer", footerContent);
// Simple equal-column layout
ComposableLayout twoCol = new ComposableLayout(2);
twoCol.slot("col1", leftContent);
twoCol.slot("col2", rightContent);
CSS Flexbox and Grid
For layouts that don't fit the pre-built components, you can use Oorian's type-safe CSS methods to build Flexbox and Grid layouts directly on any element:
// Flexbox layout
Div flexContainer = new Div();
flexContainer.setDisplay(Display.FLEX);
flexContainer.setFlexDirection(FlexDirection.ROW);
flexContainer.setJustifyContent(JustifyContent.SPACE_BETWEEN);
flexContainer.setAlignItems(AlignItems.CENTER);
flexContainer.setGap("16px");
// Grid layout
Div gridContainer = new Div();
gridContainer.setDisplay(Display.GRID);
gridContainer.addStyleAttribute("grid-template-columns", "repeat(3, 1fr)");
gridContainer.setGap("24px");
// Child element flex properties
Div sidebar = new Div();
sidebar.setFlex("0 0 250px"); // Fixed width sidebar
Div main = new Div();
main.setFlex("1"); // Flexible main content
Use Container for centered, width-constrained content.
Use RegionLayout for classic application shells with header, sidebar, and footer.
Use HStack/VStack for simple horizontal or vertical arrangements.
Use ComposableLayout for custom grid-based layouts.
Fall back to direct CSS Flexbox/Grid for anything more specialized.
12. Drag and Drop
Oorian provides a complete drag-and-drop system built on the HTML5 Drag and Drop API. Elements can be made draggable, containers can accept drops, and all events are handled server-side through standard Oorian listeners — no JavaScript required.
Making Elements Draggable
Call setDraggable(true) on any element to allow the user to drag it:
Div card = new Div();
card.setText("Drag me");
card.setDraggable(true);
This sets the HTML draggable attribute and registers the necessary client-side
handlers automatically.
Creating Drop Targets
Call setDropAllowed(true) on a container to make it accept dropped elements:
Div dropZone = new Div();
dropZone.setDropAllowed(true);
Drag and Drop Events
Oorian fires a series of events during a drag-and-drop operation. Implement
DragDropListener and register for the events you need:
| Event | Fired When |
|---|---|
DragStartEvent |
The user begins dragging an element |
DragEndEvent |
The drag operation ends (drop or cancel) |
DragEnterEvent |
A dragged element enters a drop target |
DragOverEvent |
A dragged element moves over a drop target |
DragLeaveEvent |
A dragged element leaves a drop target |
DragExitEvent |
A dragged element exits the browser window |
DropEvent |
An element is dropped on a valid target |
Handling Drops
Register listeners on both the draggable elements and the drop target, then handle the events on the server:
public class TaskBoardPage extends HtmlPage implements DragDropListener
{
private Div todoColumn;
private Div doneColumn;
@Override
protected void createBody(Body body)
{
todoColumn = new Div();
todoColumn.setDropAllowed(true);
todoColumn.registerListener(this, DropEvent.class);
doneColumn = new Div();
doneColumn.setDropAllowed(true);
doneColumn.registerListener(this, DropEvent.class);
// Create draggable task cards
for (String task : tasks)
{
Div card = createTaskCard(task);
card.setDraggable(true);
todoColumn.addElement(card);
}
body.addElement(todoColumn);
body.addElement(doneColumn);
}
@Override
public void onEvent(DropEvent event)
{
Element dropped = event.getDroppedElement();
Element newParent = event.getNewParent();
Element oldParent = event.getOldParent();
// Update your data model based on the move
updateTaskStatus(dropped, newParent);
sendUpdate();
}
}
The DropEvent
The DropEvent provides full context about the drop operation:
| Method | Description |
|---|---|
getDroppedElement() |
The element that was dropped |
getOldParent() |
The original parent container before the drag |
getNewParent() |
The container the element was dropped into |
getOldParentChildIds() |
Child element IDs of the old parent after the move |
getNewParentChildIds() |
Child element IDs of the new parent after the move |
The child ID lists reflect the new ordering after the drop, making it straightforward to persist the updated order to a database.
DropPanel
For common reordering scenarios, Oorian provides DropPanel — a container
that automatically makes its children draggable and handles drop events. Extend it and
implement three callback methods:
public class SortableList extends DropPanel
{
@Override
protected void onDragStart(Element element)
{
element.setOpacity(50); // Visual feedback
}
@Override
protected void onDragEnd(Element element)
{
element.setOpacity(100); // Restore
}
@Override
protected void onDrop(Element dropped)
{
// Element has already been moved in the DOM
// Persist the new ordering
saveOrder();
sendUpdate();
}
}
DropPanel handles all the wiring automatically: children added to the panel
become draggable, the panel itself is a drop target, and elements are visually repositioned
during the drag. The client-side auto-scroll feature activates when the user drags near
the edges of the browser window, making it easy to reorder long lists.
Use DropPanel when you need sortable lists or reorderable grids within
a single container. Use the low-level setDraggable() /
setDropAllowed() API when you need to drag between separate containers
or implement custom drag-and-drop behavior.
13. JavaScript and Browser APIs
Oorian provides type-safe Java access to browser capabilities through three layers:
methods on HtmlPage, methods on Element, and 19 specialized
API classes in the com.oorian.html.js package. You never need to write
JavaScript—every browser feature is accessible from Java.
Adding Scripts to the Page
The JavaScript element represents an HTML <script> tag.
Use it to include external JavaScript files or embed inline code. This is primarily used
when integrating third-party libraries or adding bridge code for extension libraries.
External Script Files
Reference an external JavaScript file by passing the URL to the constructor or calling
setSrc(). Absolute paths within the application are automatically resolved
with the servlet context path:
// Via constructor
JavaScript script = new JavaScript("/js/chart-config.js");
head.addElement(script);
// Via setSrc()
JavaScript analytics = new JavaScript();
analytics.setSrc("/js/analytics.js");
head.addElement(analytics);
Inline JavaScript Code
Embed JavaScript directly in the page using setSrcCode() to set the content
or addSrcCode() to append incrementally:
// Set inline code
JavaScript init = new JavaScript();
init.setSrcCode("document.addEventListener('DOMContentLoaded', initApp);");
head.addElement(init);
// Build code incrementally
JavaScript config = new JavaScript();
config.addSrcCode("var CONFIG = {\n");
config.addSrcCode(" apiUrl: '/api/v1',\n");
config.addSrcCode(" debug: false\n");
config.addSrcCode("};\n");
head.addElement(config);
Loading Source Code from a File
Use setSrcCodeFromFile() to load JavaScript source code from a file
within the web application and embed it inline at render time. Unlike setSrc()
which creates a <script src="..."> reference, this reads the file
contents and inlines them directly into the page:
// File contents are read at render time and embedded inline
JavaScript bridge = new JavaScript();
bridge.setSrcCodeFromFile("/js/oorian-bridge.js");
head.addElement(bridge);
Async and Deferred Loading
Control when external scripts execute using setAsync() and
setDefer(). These only apply to external scripts (with a src
attribute):
// Async: download in parallel, execute as soon as available
JavaScript async = new JavaScript("/js/analytics.js");
async.setAsync(true);
// Defer: download in parallel, execute after document is parsed
JavaScript deferred = new JavaScript("/js/app.js");
deferred.setDefer(true);
| Method | Description |
|---|---|
new JavaScript(src) |
Create a script referencing an external file |
setSrc(url) |
Set or change the external file URL |
setSrcCode(code) |
Set inline code (replaces any existing content) |
addSrcCode(code) |
Append inline code incrementally |
setSrcCodeFromFile(path) |
Load file contents and embed inline at render time |
setAsync(true) |
Download in parallel, execute immediately when available |
setDefer(true) |
Download in parallel, execute after document is parsed |
setType(type) |
Set the MIME type (e.g., "module" for ES6 modules) |
Executing JavaScript
When you need to call a browser API that Oorian doesn't wrap directly, use
executeJs() on HtmlPage to run JavaScript on the client:
// Execute a raw JavaScript statement
executeJs("console.log('Hello from the server')");
// Call a function with parameters (auto-serialized to JSON)
executeJs("showNotification", "Success", "Changes saved.");
// Load a JavaScript file (cached, duplicates ignored)
loadScript("/js/analytics.js");
// Load with cache-busting
loadScript("/js/analytics.js", true);
Navigation
HtmlPage provides a full navigation API:
| Method | Description |
|---|---|
navigateTo(url) |
Navigate to the specified URL |
navigateBack() / navigateBack(n) |
Go back one or n pages in browser history |
navigateForward() / navigateForward(n) |
Go forward one or n pages in browser history |
navigateToReferrer() |
Navigate to the referring page |
reload() |
Reload the current page |
navigateToAfterDelay(url, ms) |
Navigate after a delay in milliseconds |
reloadAfterDelay(ms) |
Reload after a delay in milliseconds |
navigateBackAfterDelay(ms) |
Go back after a delay in milliseconds |
historyPushState(title, url) |
Add a history entry without navigating |
historyReplaceState(title, url) |
Replace the current history entry without navigating |
navigateTo("/dashboard");
// Update the URL without a full navigation
historyPushState("User Profile", "/users/42");
// Redirect after showing a success message
navigateToAfterDelay("/dashboard", 2000);
Window Management
| Method | Description |
|---|---|
openInNewWindow(url) |
Open a URL in a new browser tab or window |
openInNewWindow(url, target) |
Open a URL in a named window |
openInNewWindow(url, name, specs) |
Open with window features (width, height, etc.) |
closeWindow(name) |
Close a named window |
blockPageUnload() |
Show a confirmation dialog when the user tries to leave |
unblockPageUnload() |
Remove the page unload confirmation |
print() |
Open the browser's print dialog |
print(url) |
Print the content at the specified URL |
Client-Side Timers and Image Preloading
| Method | Description |
|---|---|
setClientTimeout(script, ms) |
Execute JavaScript after a delay (like setTimeout) |
setClientInterval(script, ms) |
Execute JavaScript repeatedly (like setInterval) |
loadImage(url) |
Preload an image for smoother UI transitions |
loadImages(urls) |
Preload multiple images |
Element-Level Methods
Individual elements provide methods for scrolling, click simulation, DOM updates, and server callbacks:
| Method | Description |
|---|---|
scrollTo(pos) |
Scroll the element to a pixel position |
scrollToTop() |
Scroll the element to the top |
scrollToBottom() |
Scroll the element to the bottom |
click() |
Simulate a click on the element from the server |
update() |
Re-render and push this element to the browser |
updateAttributes() |
Push only attribute changes (more efficient) |
recreate() |
Full re-render from scratch |
refresh() |
Refresh this element and all its children |
requestCallback() |
Schedule a server round-trip callback |
requestCallback(id) |
Callback with a named identifier |
requestCallback(id, delay) |
Delayed callback with a named identifier |
The callback system enables server-initiated round-trips. Override onCallback(String callbackId)
to handle the response:
myElement.requestCallback("refresh", 5000);
@Override
public void onCallback(String callbackId)
{
if ("refresh".equals(callbackId))
{
refreshData();
}
}
Specialized JavaScript API Classes
Oorian provides 19 static API classes in com.oorian.html.js for accessing
browser features. All follow the same pattern: static methods, event-driven results,
and no instantiation required.
| API Class | Purpose |
|---|---|
WindowApi |
Window management, alerts, scrolling, print, focus/blur |
NavigationApi |
URL navigation, history, back/forward, hash manipulation |
TimerApi |
Client-side timers and server-side scheduled tasks |
ScreenApi |
Screen size, window size, device type, orientation, pixel ratio |
SelectionApi |
Text selection and focus management |
ClipboardApi |
Read and write the system clipboard |
StorageApi |
Browser localStorage and sessionStorage |
FullscreenApi |
Enter, exit, and toggle fullscreen mode |
GeolocationApi |
GPS position requests and continuous tracking |
NotificationApi |
Desktop notifications with permission management |
SpeechApi |
Text-to-speech synthesis and speech recognition |
ShareApi |
Native content sharing (mobile share sheets) |
MediaDevicesApi |
Camera and microphone enumeration and access |
PermissionsApi |
Query browser permission status |
NetworkApi |
Online/offline status monitoring |
VisibilityApi |
Page visibility changes (tab switching, minimization) |
VibrationApi |
Haptic feedback patterns for mobile devices |
WakeLockApi |
Prevent screen dimming for video players, presentations |
Every API method has two forms: a convenience version that finds the current page
automatically, and an explicit version that accepts an HtmlPage parameter.
Event-Driven Pattern
Asynchronous browser APIs deliver results through Oorian's standard event system. Register a listener, call the API, and handle the result:
public class MapPage extends HtmlPage implements GeolocationListener
{
@Override
protected void createBody(Body body)
{
body.registerListener(this, GeolocationEvent.class);
body.registerListener(this, GeolocationErrorEvent.class);
GeolocationApi.getCurrentPosition(body);
}
@Override
public void onEvent(GeolocationEvent event)
{
double lat = event.getLatitude();
double lon = event.getLongitude();
updateMap(lat, lon);
}
@Override
public void onEvent(GeolocationErrorEvent event)
{
showError(event.getMessage());
}
}
Common API Examples
// Copy text to clipboard
ClipboardApi.writeText("Copied!");
// Store data in the browser
StorageApi.setLocalItem("theme", "dark");
StorageApi.setSessionItem("cart", cartJson);
// Show a desktop notification
NotificationApi.requestPermission(body);
NotificationApi.show(body, "New Message", "You have 3 unread messages");
// Detect device type
if (ScreenApi.isMobile())
{
useMobileLayout();
}
// Check screen orientation
boolean portrait = ScreenApi.isPortrait();
// Enter fullscreen mode
FullscreenApi.requestFullscreen(videoPlayer);
// Toggle fullscreen
FullscreenApi.toggleFullscreen(presentationDiv);
// Text-to-speech
SpeechApi.speak("Welcome to the application");
SpeechApi.speak("Bienvenue", "fr-FR", 1.0f, 1.0f);
// Haptic feedback on mobile
VibrationApi.success();
VibrationApi.error();
// Native sharing on mobile
ShareApi.shareUrl(body, "Check this out", "https://oorian.com");
// Monitor online/offline status
NetworkApi.startListening(body);
// Detect tab visibility changes
VisibilityApi.startListening(body);
// Keep the screen on during a presentation
WakeLockApi.request(body);
// Release when done
WakeLockApi.release(body);
// Execute a task on the server after a delay
TimerApi.setTimeout(() -> {
statusLabel.setText("Operation complete");
sendUpdate();
}, 5000);
// Repeat a task every 30 seconds
TimerApi.setInterval(() -> {
refreshDashboard();
sendUpdate();
}, 0, 30000);
14. Worker Threads
Sometimes you need to perform long-running operations — database queries, API calls, file
processing, report generation — without blocking the user's request. Oorian's
OorianWorkerThread lets you offload work to a background thread and update the UI
when it completes.
Creating a Worker Thread
Call OorianWorkerThread.create() from an event handler, passing a lambda that
contains the work to perform. The worker thread has full access to the page's elements, so you
can update the UI directly and push changes to the browser with sendUpdate().
@Override
public void onEvent(MouseClickedEvent event)
{
statusLabel.setText("Processing...");
progressBar.setDisplay(Display.BLOCK);
sendUpdate();
OorianWorkerThread.create(() ->
{
// This runs in a background thread
List<Report> reports = generateReports();
// Update the UI when done
statusLabel.setText("Complete! Generated " + reports.size() + " reports.");
progressBar.setDisplay(Display.NONE);
sendUpdate(); // Push changes to browser
});
}
How It Works
Worker threads created with OorianWorkerThread.create() are queued and started
after the current request processing completes. This design prevents race conditions by ensuring
your event handler finishes before the background thread begins modifying page state.
Key characteristics of worker threads:
- Queued execution — The worker starts after the current request completes, not immediately
- Full page access — The background thread can read and modify any element on the page
- UI push via sendUpdate() — Call
sendUpdate()to push changes to the browser at any point during execution - No concurrent access — Only one worker thread runs per page at a time, preventing race conditions on shared page state
Worker threads that call sendUpdate() require a persistent connection to the
browser. Use WEBSOCKET or AJAX_WITH_SSE mode for pages that use
worker threads. With AJAX_ONLY, the browser won't receive updates until its
next poll.
15. Extension Libraries
Oorian's extension libraries are Java wrappers for popular JavaScript UI libraries. Each wrapper gives you a clean Java API while preserving the full power of the underlying library. There are over 50 wrappers available across a wide range of categories.
Available Categories
- Full UI Platforms — Webix, DHTMLX, Syncfusion, Kendo UI, Wijmo, DevExtreme
- Web Components & CSS Frameworks — Shoelace, Web Awesome, Bootstrap, Tailwind CSS
- Rich Text Editors — CKEditor, TinyMCE, Quill, ProseMirror, Monaco Editor, TipTap, CodeMirror, Froala
- Data Grids — AG Grid, Handsontable, Tabulator, DataTables
- Charts — Chart.js, Highcharts, ECharts, D3.js, ApexCharts, Plotly.js, AnyChart
- Maps — Leaflet, Mapbox, OpenLayers, Google Maps
- Scheduling — FullCalendar, vis.js
- And more — Diagrams, document viewers, carousels, notifications, media players, image tools, tour/onboarding
Using an Extension Library
Each extension library provides a static addAllResources() method that loads
the required CSS and JavaScript files. Then you use the wrapper classes just like any other
Oorian element. Here is a complete example using Chart.js:
@Page("/analytics")
public class AnalyticsPage extends HtmlPage
{
@Override
protected void createHead(Head head)
{
setTitle("Analytics");
ChartJs.addAllResources(head);
}
@Override
protected void createBody(Body body)
{
CjsChart chart = new CjsChart(CjsChart.BAR);
chart.setWidth("600px");
chart.setHeight("400px");
CjsDataSet sales = chart.addDataSet("Monthly Sales");
sales.setData(120, 190, 300, 500, 200, 300);
chart.setLabels("Jan", "Feb", "Mar", "Apr", "May", "Jun");
body.addElement(chart);
}
}
Consistent Conventions
Every Oorian wrapper follows the same conventions, so once you learn the pattern with one library, every other library feels familiar:
- Java-first configuration — All options are set through Java methods, not JavaScript objects or JSON
- Event integration — Library-specific events are dispatched through Oorian's standard listener system
- Resource management — A single
addAllResources()call loads everything the library needs - Fluent API — Setters return
thisfor method chaining where appropriate
For the full list of extension libraries and their documentation, see the Extensions page.
16. HTML Templates
While Oorian encourages building UIs programmatically in Java, it also supports HTML
templates for cases where you want to start with existing HTML markup.
HtmlTemplatePage loads an HTML file, parses it into a live Oorian element tree,
and lets you modify anything from Java.
HtmlTemplatePage
Extend HtmlTemplatePage instead of HtmlPage and pass the path
to your HTML file. Oorian parses the template's <head> and
<body> into real Oorian elements, then calls createHead()
and createBody() so you can modify them:
@Page("/dashboard")
public class DashboardPage extends HtmlTemplatePage
{
public DashboardPage()
{
super("/templates/dashboard.html");
}
@Override
protected void createBody(Body body)
{
// Find elements from the template and modify them
Element title = getElementById("page-title");
title.setText("User Dashboard");
// Add Oorian components into template placeholders
Element content = getElementById("content");
content.addElement(createStatsPanel());
content.addElement(createRecentActivity());
}
}
The corresponding template file (web/templates/dashboard.html):
<!DOCTYPE html>
<html lang="en">
<head>
<title>Dashboard</title>
<link rel="stylesheet" href="/css/dashboard.css">
</head>
<body>
<div class="layout">
<header>
<h1 id="page-title">Placeholder</h1>
</header>
<main id="content">
<!-- Oorian components will be added here -->
</main>
<footer>Copyright 2026</footer>
</div>
</body>
</html>
How It Works
When the page is requested, HtmlTemplatePage automatically:
- Loads the HTML file from the servlet context
- Parses it with Oorian's
Html5Parserinto a full element tree - Copies attributes from the template's
<html>element (e.g.,lang,dir) to the page - Extracts the
<head>and<body>and sets them as the page's head and body - Calls
createHead()andcreateBody()for your modifications
Every element in the parsed tree is a real Oorian element — you can register event
listeners, apply styles, add child components, and use sendUpdate() to push
changes, just like elements created in Java.
Finding and Modifying Elements
Use getElementById() to access specific elements by their id
attribute, or getElementsByTagName() to find elements by tag name:
@Override
protected void createBody(Body body)
{
// Find a single element by ID
Element sidebar = getElementById("sidebar");
sidebar.addElement(createNavMenu());
// Find all elements by tag name
Elements buttons = body.getElementsByTagName("button", true);
for (Element button : buttons)
{
button.registerListener(this, MouseClickedEvent.class);
}
}
Modifying the Head
Override createHead() to add resources or meta tags to the template's
existing head content:
@Override
protected void createHead(Head head)
{
// The head already contains everything from the template.
// Add additional resources as needed.
head.addCssLink("/css/extra-styles.css");
head.addElement(new JavaScript("/js/analytics.js"));
}
Character Encoding
Templates are read as UTF-8 by default. To use a different encoding, call
setCharset() in your constructor:
public LegacyPage()
{
super("/templates/legacy.html");
setCharset("ISO-8859-1");
}
Parsing HTML Directly
For cases where you need to parse an HTML string rather than load a file, use
Html5Parser directly. The parser supports all standard HTML5 elements, void
elements, CDATA sections, entity references, and nested content:
Html5Parser parser = new Html5Parser();
Element content = parser.parse(
"<div class='card'>" +
" <h2>Welcome</h2>" +
" <p>Hello, world!</p>" +
"</div>"
);
body.addElement(content);
Templates are useful when migrating existing HTML to Oorian, working with designers who provide HTML mockups, or when a layout is complex enough that HTML is more readable than programmatic construction. Since parsed elements are fully live Oorian elements, you get the best of both worlds — HTML for structure, Java for behavior.
17. Standalone HTML Generation
Oorian can be used as a standalone library to generate HTML without running a web server. This is ideal for generating emails, newsletters, reports, invoices, and any other content that needs properly structured HTML.
Generating HTML for Emails
Create Oorian elements, style them, and call getInnerHtml() to produce an HTML
string that you can pass to your email sending library:
Div container = new Div();
container.setMaxWidth("600px");
container.setMargin("0 auto");
container.setFontFamily("Arial, sans-serif");
H1 title = new H1();
title.setText("Welcome to Our Service!");
title.setColor("#1f2937");
container.addElement(title);
P greeting = new P();
greeting.setText("Hello " + userName + ",");
container.addElement(greeting);
P body = new P();
body.setText("Thank you for signing up. Click the link below to verify your account.");
container.addElement(body);
A link = new A(verificationUrl);
link.setText("Verify Your Account");
link.setBackgroundColor("#2563eb");
link.setColor("#ffffff");
link.setPadding("12px 24px");
link.setBorderRadius("6px");
link.addStyleAttribute("text-decoration", "none");
link.setDisplay(Display.INLINE_BLOCK);
container.addElement(link);
// Get the HTML string
String html = container.getInnerHtml();
Benefits
Using Oorian for HTML generation gives you the same advantages as building web pages:
- Type-safe — Compile-time checking prevents HTML errors
- Reusable components — Create email templates as reusable Java classes
- Full IDE support — Autocomplete and refactoring for every element and style
- No template syntax — No Thymeleaf, FreeMarker, or Velocity to learn
- Testable — Unit test your HTML generation with standard Java testing frameworks
When using Oorian for standalone HTML generation, you only need the core Oorian library. No servlet container, no web server, and no frontend toolchain are required.
18. Self-Contained Web Apps
LaunchPad is Oorian's embedded server, powered by Jetty. It lets you run Oorian applications as standalone Java programs without deploying to an external servlet container. Just run your application like any Java program.
Quick Start
Create a standard Java main() method, instantiate a LaunchPad,
and call launch() with your Application class:
public class Main
{
public static void main(String[] args)
{
new LaunchPad()
.setPort(8080)
.launch(MyApplication.class);
}
}
That's it. Your application starts on http://localhost:8080 with full support
for AJAX, SSE, and WebSocket communication.
Configuration
LaunchPad provides a fluent API for configuring the embedded server. Every setter returns the LaunchPad instance for method chaining:
new LaunchPad()
.setPort(8080)
.setContextPath("/myapp")
.enableSsl("/path/to/keystore.jks", "password")
.setSslPort(8443)
.setThreadPool(10, 50)
.setIdleTimeout(30000)
.setGzipEnabled(true)
.setHealthEndpointEnabled(true)
.setHealthEndpointPath("/health")
.launch(MyApplication.class);
| Method | Description |
|---|---|
setPort(int) |
Sets the HTTP port (default: 8080) |
setContextPath(String) |
Sets the application context path (default: "/") |
enableSsl(String, String) |
Enables HTTPS with keystore path and password |
setSslPort(int) |
Sets the HTTPS port (default: 8443) |
setThreadPool(int, int) |
Sets the minimum and maximum thread pool size |
setGzipEnabled(boolean) |
Enables or disables GZIP response compression |
setHealthEndpointEnabled(boolean) |
Enables a health check endpoint for monitoring |
setConfigFile(String) |
Loads configuration from a properties file |
Configuration File
Instead of configuring LaunchPad programmatically, you can use a properties file for settings that may change between environments:
# server.properties
server.port=8080
server.context-path=/myapp
server.ssl.enabled=true
server.ssl.keystore=/path/to/keystore.jks
server.ssl.keystore-password=secret
server.ssl.port=8443
server.threads.min=10
server.threads.max=50
server.gzip.enabled=true
server.health.enabled=true
server.health.path=/health
Load the configuration file with setConfigFile():
new LaunchPad()
.setConfigFile("server.properties")
.launch(MyApplication.class);
When both a config file and programmatic settings are provided, the programmatic settings override the file. This lets you use a config file for defaults and override specific settings in code.
19. JSON
Oorian includes a built-in JSON library for parsing, generating, and manipulating
JSON data. The core classes are JsonObject, JsonArray,
and the Jsonable interface for creating typed data models.
JsonObject
Use JsonObject to build JSON objects with key-value pairs. The
put() method accepts strings, numbers, booleans, nested objects, arrays,
and any object that implements Jsonable:
// Build a JSON object
JsonObject user = new JsonObject();
user.put("name", "Alice Johnson");
user.put("age", 32);
user.put("active", true);
user.put("balance", 1250.75);
// Nested objects
JsonObject address = new JsonObject();
address.put("city", "Portland");
address.put("state", "OR");
user.put("address", address);
// Convert to JSON string
String json = user.toJsonString();
// {"name":"Alice Johnson","age":32,"active":true,"balance":1250.75,"address":{"city":"Portland","state":"OR"}}
Reading Values
Parse a JSON string with JsonObject.parse(), then use typed getters
to extract values. Each getter has an overload that accepts a default value:
// Parse from a JSON string
JsonObject json = JsonObject.parse(jsonString);
// Typed getters (return null if key is missing)
String name = json.getAsString("name");
Integer age = json.getAsInt("age");
Boolean active = json.getAsBoolean("active");
Double balance = json.getAsDouble("balance");
Long timestamp = json.getAsLong("timestamp");
// Getters with default values
String email = json.getAsString("email", "unknown@example.com");
Integer count = json.getAsInt("count", 0);
Boolean verified = json.getAsBoolean("verified", false);
// Nested objects and arrays
JsonObject address = json.getAsJsonObject("address");
String city = address.getAsString("city");
JsonArray tags = json.getAsJsonArray("tags");
// Check if a key exists
if (json.containsKey("email"))
{
// Key exists in the object
}
// Get all keys
List<String> keys = json.getKeys();
JsonArray
JsonArray represents an ordered collection of JSON values. It supports
the fluent add() method and provides typed extraction of elements:
// Build a JSON array
JsonArray colors = new JsonArray();
colors.add("red").add("green").add("blue");
// Add objects to an array
JsonArray items = new JsonArray();
items.add(new JsonObject("name", "Item 1"));
items.add(new JsonObject("name", "Item 2"));
// Parse a JSON array string
JsonArray numbers = JsonArray.parse("[1, 2, 3, 4, 5]");
// Read individual elements by index
String first = colors.get(0).asString().getValue();
int num = numbers.get(0).asNumber().getValue().intValue();
// Extract typed lists
List<String> colorList = colors.getStringArray();
List<Integer> numberList = numbers.getIntArray();
// Iterate over array elements
for (JsonValue value : items)
{
JsonObject item = (JsonObject) value;
String name = item.getAsString("name");
}
// Static convenience methods for parsing typed arrays
List<String> names = JsonArray.parseStrings("[\"Alice\", \"Bob\", \"Carol\"]");
List<Integer> ids = JsonArray.parseInts("[1, 2, 3]");
When extracting string values from a JsonArray by index, use
get(i).asString().getValue() to get the raw string. The
getAsString(i) method returns the JSON representation which may include
surrounding quotes. For bulk extraction, use getStringArray() which
returns a properly unwrapped List<String>.
The Jsonable Interface
For structured data, implement the Jsonable interface to create typed
Java classes that serialize to and from JSON. This is the recommended approach —
use Jsonable classes instead of working with raw JsonObject
instances for your application data.
public class Product implements Jsonable
{
private String name;
private double price;
private int quantity;
private boolean inStock;
public Product()
{
}
public Product(JsonValue jv)
{
initFromJson(jv);
}
@Override
public final void initFromJson(JsonValue jv)
{
JsonObject json = (JsonObject) jv;
this.name = json.getAsString("name");
this.price = json.getAsDouble("price");
this.quantity = json.getAsInt("quantity");
this.inStock = json.getAsBoolean("inStock");
}
@Override
public JsonValue toJsonValue()
{
JsonObject json = new JsonObject();
json.put("name", name);
json.put("price", price);
json.put("quantity", quantity);
json.put("inStock", inStock);
return json;
}
// Setters and getters...
}
The Jsonable pattern requires two constructors and two methods:
- Default constructor — for creating empty instances
- JsonValue constructor — calls
initFromJson(jv)for direct instantiation from JSON - initFromJson() — deserializes fields from a
JsonValue(mark asfinal) - toJsonValue() — serializes fields to a
JsonObject
Once a class implements Jsonable, it integrates seamlessly with
JsonObject and JsonArray:
// Add a Jsonable object to a JsonObject
Product product = new Product();
product.setName("Widget");
product.setPrice(29.99);
JsonObject order = new JsonObject();
order.put("product", product); // Automatically calls toJsonValue()
// Add Jsonable objects to a JsonArray
JsonArray catalog = new JsonArray();
catalog.add(product1); // Automatically calls toJsonValue()
catalog.add(product2);
// Deserialize from a JSON string
Product parsed = new Product(JsonObject.parse(jsonString));
// Convert to JSON string
String json = product.toJsonString();
Always provide both constructors (default and JsonValue). Mark
initFromJson as final to prevent subclasses from
overriding the deserialization logic. If the default constructor has initialization
logic needed by deserialization (e.g., instantiating nested objects), the JsonValue
constructor should call this() first, then initFromJson(jv).
20. XML
Oorian includes a built-in XML library for building, parsing, and navigating XML
documents. The core classes are XmlDocument for complete documents and
XmlElement for individual elements.
Building XML Documents
Create XML documents programmatically using XmlElement and
XmlDocument. The fluent API makes it easy to construct nested structures:
// Build the XML tree
XmlElement root = new XmlElement("catalog");
root.addAttribute("version", "2.0");
XmlElement book = new XmlElement("book");
book.addAttribute("id", "1");
XmlElement title = new XmlElement("title");
title.setText("Effective Java");
book.addChild(title);
XmlElement author = new XmlElement("author");
author.setText("Joshua Bloch");
book.addChild(author);
XmlElement price = new XmlElement("price");
price.addAttribute("currency", "USD");
price.setText("45.00");
book.addChild(price);
root.addChild(book);
// Wrap in a document with XML declaration
XmlDocument doc = new XmlDocument(root);
doc.setVersion("1.0");
doc.setEncoding("UTF-8");
// Output formatted XML
String xml = doc.toXmlString();
The output:
<?xml version="1.0" encoding="UTF-8"?>
<catalog version="2.0">
<book id="1">
<title>Effective Java</title>
<author>Joshua Bloch</author>
<price currency="USD">45.00</price>
</book>
</catalog>
XmlElement also supports CDATA sections for content that should not
be parsed:
XmlElement description = new XmlElement("description");
description.addCData("Contains <special> characters & markup");
root.addChild(description);
Parsing XML
Oorian can parse XML from strings, files, and input streams:
// Parse from a string
XmlDocument doc = XmlDocument.parse(xmlString);
// Load from a file path
XmlDocument doc = XmlDocument.loadFromFile("/data/config.xml");
// Load from a File object
XmlDocument doc = XmlDocument.loadFromFile(new File("config.xml"));
// Load from an InputStream
XmlDocument doc = XmlDocument.loadFromStream(inputStream);
Navigating XML
Once parsed, navigate the XML tree using XmlElement methods to find
elements, read attributes, and extract text content:
XmlDocument doc = XmlDocument.parse(xmlString);
XmlElement root = doc.getRoot();
// Get a single child element by name
XmlElement firstBook = root.getChildByName("book");
// Get all children with a specific name
List<XmlElement> allBooks = root.getChildrenByName("book");
// Read attributes
String bookId = firstBook.getAttribute("id");
// Check for attribute existence
if (firstBook.hasAttribute("isbn"))
{
String isbn = firstBook.getAttribute("isbn");
}
// Read text content of child elements
String title = firstBook.getChildByName("title").getText();
String author = firstBook.getChildByName("author").getText();
// Navigate the tree
for (XmlElement book : allBooks)
{
String bookTitle = book.getChildByName("title").getText();
String bookAuthor = book.getChildByName("author").getText();
XmlElement priceElement = book.getChildByName("price");
String currency = priceElement.getAttribute("currency");
String amount = priceElement.getText();
}
// Access parent element
XmlElement parent = firstBook.getParent(); // returns the root element
You can also save documents back to files:
// Save to a file
doc.saveToFile("/data/output.xml");
// Save to a File object
doc.saveToFile(new File("output.xml"));
The XML library also supports modifying existing documents. You can add, remove, and replace elements and attributes:
// Add a new child element
XmlElement newBook = new XmlElement("book");
newBook.addAttribute("id", "2");
newBook.addChild(new XmlElement("title").setText("Clean Code"));
newBook.addChild(new XmlElement("author").setText("Robert C. Martin"));
root.addChild(newBook);
// Remove an attribute
firstBook.removeAttribute("deprecated");
// Remove a child element
root.removeChild(oldBook);
// Remove all children
root.removeAllChildren();
// Rename an element
firstBook.setName("publication");
The XML library handles CDATA sections, attribute escaping, and proper XML
formatting with indentation. It produces well-formed XML output with entity
encoding for special characters (&, <,
>, ", ').
21. Accessibility
Oorian provides built-in support for building accessible web applications. ARIA attributes are available on every element, and dedicated accessibility components handle common patterns like skip links, live regions, and focus traps.
ARIA Attributes
Every Oorian element supports ARIA attributes through type-safe methods on the
Element base class:
Button menuButton = new Button();
menuButton.setRole("menubutton");
menuButton.setAriaLabel("Open main menu");
menuButton.setAriaExpanded(false);
menuButton.setAriaHasPopup(true);
Div dialog = new Div();
dialog.setRole(AriaRole.DIALOG);
dialog.setAriaLabelledBy("dialog-title");
dialog.setAriaDescribedBy("dialog-description");
TextInput emailInput = new TextInput();
emailInput.setAriaInvalid(true);
emailInput.setAriaDescribedBy("email-error");
Div decorative = new Div();
decorative.setAriaHidden(true);
Div tab = new Div();
tab.setAriaSelected(true);
tab.setTabIndex(0);
Skip Links
Skip links allow keyboard users to bypass navigation and jump directly to the main content.
Oorian's SkipLink component handles the standard visually-hidden-until-focused
pattern:
@Override
protected void createBody(Body body)
{
// Add skip link as the first element in the body
body.addElement(new SkipLink("main-content", "Skip to main content"));
// Navigation...
body.addElement(createNavigation());
// Main content with matching ID
Div mainContent = new Div();
mainContent.setId("main-content");
mainContent.setTabIndex(-1);
body.addElement(mainContent);
}
Live Regions
Live regions announce dynamic content changes to screen readers without moving focus. Use them for status messages, error notifications, and progress updates:
// Create a polite live region (default) for status messages
LiveRegion statusRegion = new LiveRegion();
body.addElement(statusRegion);
// For urgent messages, use assertive mode
LiveRegion alertRegion = new LiveRegion(AriaLive.ASSERTIVE);
body.addElement(alertRegion);
// In an event handler, announce a message
statusRegion.announce("File saved successfully");
// For critical alerts
alertRegion.announce("Session will expire in 1 minute");
Focus Traps
Focus traps keep keyboard navigation within a container element, which is essential for modal dialogs. When activated, Tab and Shift+Tab cycle only through the focusable elements inside the container:
// Create a modal dialog
Div modal = new Div();
modal.setRole(AriaRole.DIALOG);
modal.setId("my-modal");
// Create a focus trap for the modal
FocusTrap focusTrap = new FocusTrap(modal);
// When showing the modal, activate the trap
focusTrap.activate();
// When closing the modal, deactivate and return focus
focusTrap.deactivate(openButton);
Visually Hidden Text
The VisuallyHidden component renders text that is invisible to sighted users
but readable by screen readers. Use it to add context to icon-only buttons or clarify
ambiguous links:
// Icon button with accessible label
Button deleteBtn = new Button();
deleteBtn.addElement(new Icon("trash"));
deleteBtn.addElement(new VisuallyHidden("Delete item"));
// Clarify a "Read more" link
A readMore = new A("/article/123");
readMore.setText("Read more");
readMore.addElement(new VisuallyHidden(" about Web Accessibility"));
22. Security
Oorian includes several built-in security features to protect your applications. Because Oorian controls both the server-side rendering and the client-server communication, it can enforce security at the framework level.
CSRF Protection
Cross-Site Request Forgery (CSRF) protection is built into Oorian. When enabled, every form submission and AJAX request automatically includes a CSRF token that the server validates. Enable it in your Application class:
@WebListener
public class MyApp extends Application
{
@Override
public void initialize(ServletContext context)
{
registerPackage("com.myapp");
setCsrfProtectionEnabled(true);
}
@Override
public void destroy(ServletContext context) { }
}
You can disable CSRF protection on specific pages that serve as public API endpoints:
@Page("/api/webhook")
public class PublicApiPage extends HtmlPage
{
@Override
protected void createBody(Body body)
{
setCsrfProtectionEnabled(false);
// ... page content
}
}
CSRF Token Management
For advanced use cases, you can access and manage CSRF tokens directly through the
OorianSession:
OorianSession session = OorianSession.get();
// Get the current CSRF token
String token = session.getCsrfToken();
// Validate a token received from the client
boolean valid = session.validateCsrfToken(submittedToken);
// Regenerate the token (e.g., after authentication)
String newToken = session.regenerateCsrfToken();
Session Security
Oorian's OorianSession wraps the servlet container's session with additional
security features. Follow these best practices:
- Set session timeouts — Configure appropriate session timeouts for your application's security requirements
- Invalidate on logout — Always call
session.invalidate()when a user logs out - Regenerate CSRF after authentication — Call
regenerateCsrfToken()after login to prevent session fixation attacks - Use secure cookies — Configure your servlet container to use secure, HttpOnly cookies for session identifiers
Authentication Patterns
Oorian does not impose an authentication framework, giving you freedom to use any library
or custom implementation. A common pattern is to create a base page class that checks
authentication in initializePage():
public abstract class ProtectedPage extends HtmlPage
{
@Override
protected boolean initializePage(
HttpServletRequest request, HttpServletResponse response)
{
User user = (User) getSession().getAttribute("user");
if (user == null)
{
navigateTo("/login");
return false;
}
return true;
}
}
// All protected pages extend ProtectedPage instead of HtmlPage
@Page("/admin/users")
public class UserManagementPage extends ProtectedPage
{
@Override
protected void createBody(Body body)
{
// Only reached if user is authenticated
body.addElement(new H1().setText("User Management"));
}
}
Content Security Policy
A Content Security Policy (CSP) tells the browser which sources of content are allowed on
your page, preventing cross-site scripting (XSS) and data injection attacks. Oorian's
ContentSecurityPolicy class provides a fluent builder for constructing CSP
headers.
Basic Usage
Build a policy and apply it to the page's Head element:
ContentSecurityPolicy csp = new ContentSecurityPolicy()
.addDefaultSrc(ContentSecurityPolicy.SELF)
.addScriptSrc(ContentSecurityPolicy.SELF, "https://cdn.example.com")
.addStyleSrc(ContentSecurityPolicy.SELF, ContentSecurityPolicy.UNSAFE_INLINE)
.addImgSrc(ContentSecurityPolicy.SELF, "data:", "https:")
.upgradeInsecureRequests();
// Apply to the page head
head.setContentSecurityPolicy(csp);
// Produces: <meta http-equiv="content-security-policy"
// content="default-src 'self'; script-src 'self' https://cdn.example.com;
// style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;
// upgrade-insecure-requests">
Source Constants
The ContentSecurityPolicy class provides constants for common CSP source
keywords:
| Constant | Value | Description |
|---|---|---|
SELF |
'self' |
Same origin only (same scheme, host, and port) |
NONE |
'none' |
Block all sources for this directive |
UNSAFE_INLINE |
'unsafe-inline' |
Allow inline scripts or styles (use sparingly) |
UNSAFE_EVAL |
'unsafe-eval' |
Allow eval() and similar (use sparingly) |
STRICT_DYNAMIC |
'strict-dynamic' |
Trust scripts loaded by already-trusted scripts |
UNSAFE_HASHES |
'unsafe-hashes' |
Allow specific inline event handlers matched by hash |
Directives
Each add* method accepts one or more source values (strings or constants).
Calling the same directive multiple times accumulates sources rather than replacing them:
ContentSecurityPolicy csp = new ContentSecurityPolicy();
// Fetch directives — control where resources can be loaded from
csp.addDefaultSrc(ContentSecurityPolicy.SELF); // Fallback for all fetch directives
csp.addScriptSrc(ContentSecurityPolicy.SELF); // JavaScript sources
csp.addStyleSrc(ContentSecurityPolicy.SELF); // Stylesheet sources
csp.addImgSrc(ContentSecurityPolicy.SELF); // Image sources
csp.addFontSrc(ContentSecurityPolicy.SELF); // Font sources
csp.addConnectSrc(ContentSecurityPolicy.SELF); // AJAX, WebSocket, SSE sources
csp.addMediaSrc(ContentSecurityPolicy.SELF); // Audio and video sources
csp.addObjectSrc(ContentSecurityPolicy.NONE); // Object/embed/applet sources
csp.addFrameSrc(ContentSecurityPolicy.SELF); // Frame and iframe sources
csp.addChildSrc(ContentSecurityPolicy.SELF); // Nested browsing and workers
csp.addWorkerSrc(ContentSecurityPolicy.SELF); // Worker script sources
csp.addManifestSrc(ContentSecurityPolicy.SELF); // Application manifest sources
// Navigation and framing directives
csp.addBaseUri(ContentSecurityPolicy.SELF); // Restrict <base> URLs
csp.addFormAction(ContentSecurityPolicy.SELF); // Restrict form submission targets
csp.addFrameAncestors(ContentSecurityPolicy.NONE); // Who can frame this page (clickjacking protection)
// Special directives
csp.upgradeInsecureRequests(); // Upgrade HTTP to HTTPS
csp.setReportUri("/csp-report"); // Violation report endpoint
csp.setReportTo("csp-group"); // Reporting API group name
csp.addSandbox("allow-scripts", "allow-forms"); // Sandbox restrictions
Nonce and Hash Sources
Instead of allowing 'unsafe-inline', use nonces or hashes to permit specific
inline scripts and styles:
// Allow a specific inline script by nonce
String myNonce = "abc123";
csp.addScriptSrc(ContentSecurityPolicy.nonce(myNonce));
// Adds: 'nonce-abc123'
// Allow a specific inline script by hash
csp.addScriptSrc(ContentSecurityPolicy.sha256("base64EncodedHash..."));
// Adds: 'sha256-base64EncodedHash...'
// SHA-384 and SHA-512 are also available
csp.addStyleSrc(ContentSecurityPolicy.sha384(hash));
csp.addStyleSrc(ContentSecurityPolicy.sha512(hash));
Report-Only Mode
Test a new policy without blocking anything. Violations are reported but content is still loaded:
// Report-only mode — violations logged but not enforced
head.setContentSecurityPolicyReportOnly(csp);
// Enforcement mode — violations are blocked
head.setContentSecurityPolicy(csp);
When deploying CSP for the first time, use setContentSecurityPolicyReportOnly()
with a setReportUri() endpoint. Monitor the violation reports to identify what your
policy needs to allow before switching to enforcement mode with
setContentSecurityPolicy().
Input Handling
Oorian provides several layers of protection for user input:
- XSS prevention — Text set via
setText()is automatically escaped. UseRawHtmlonly when you explicitly need unescaped content - No direct SQL — Oorian doesn't include a database layer. Use parameterized queries with your preferred database library to prevent SQL injection
- Validated forms — Use
ValidatedFormfor server-side input validation on all user-submitted data
Client-side validation improves user experience, but server-side validation is the security
boundary. Never trust client-submitted data without server-side validation. Oorian's
ValidatedForm validates on the server, ensuring malicious clients cannot bypass
validation rules.