Home / Documentation / User's Guide

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.

Draft

This guide is a work in progress and will be revised regularly until the official launch of Oorian.

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
New to Oorian?

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:

BASH
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:

Java
@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:

Java
@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.

Quick Start Guide

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:

Java
@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:

Java
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
Default Communication Mode

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:

Java
// 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:

Java
@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:

  1. initializePage() — Called first. Perform authentication checks, load data, or redirect. Return false to stop page rendering.
  2. createHead() — Build the <head> section: title, meta tags, stylesheets, and scripts.
  3. createBody() — Build the <body> section: your page content.
  4. 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:

Java
@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:

Java
@Override
public void onEvent(MouseClickedEvent event)
{
    statusLabel.setText("Processing...");
    progressBar.setWidth("50%");
    submitBtn.setDisabled(true);

    sendUpdate();
}
When is sendUpdate() required?

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:

Java
@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:

Java
// 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");
Navigation and Page Lifecycle

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:

Java
// 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:

Java
// 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():

Java
@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():

Java
@Override
protected boolean initializePage(
    HttpServletRequest request, HttpServletResponse response)
{
    // Prevent caching of sensitive pages
    response.setHeader("Cache-Control",
        CacheControl.preventCaching().toString());

    return true;
}
CssFile Cache Defaults

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
H1H6 <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:

Java
// 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():

Java
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
Java
// 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:

Java
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);
Never instantiate Element directly

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:

Java
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():

Java
// 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:

Java
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:

Java
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:

Java
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:

Java
@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:

Java
@Override
protected void createHead(Head head)
{
    CommonStyles.addStyleSheet(head);  // Links to /css/common.css
}
Prefer built-in style methods

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.

Java
@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.

Java
@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.

Java
@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:

Java
// 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:

Java
@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);
    }
}
Default Mode

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:

Java
@Override
public void onEvent(MouseClickedEvent event)
{
    statusLabel.setText("Processing...");
    progressBar.setWidth("50%");
    submitBtn.setDisabled(true);

    sendUpdate();  // Push all three changes in one batch
}
Tip

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:

Java
@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.

Routing Events with getSource()

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:

  1. The browser sends an event to the server (click, form submit, input change, etc.)
  2. Oorian dispatches the event to your listener
  3. Your handler modifies element properties (text, visibility, styles, etc.)
  4. 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.

When to Call sendUpdate()

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:

Java
// 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");
Always Use OorianSession

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:

Java
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
Java
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:

Java
// 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);
Never Store Sensitive Data in Cookies

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:

Java
@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
Java
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:

Java
// 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:

Java
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:

Java
@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:

Java
// 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:

Java
// 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:

STRUCTURE
+--------------------------------------+
|               NORTH                  |
+--------+-----------------+-----------+
|        |                 |           |
|  WEST  |     CENTER      |   EAST    |
|        |                 |           |
+--------+-----------------+-----------+
|               SOUTH                  |
+--------------------------------------+
Java
// 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:

Java
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:

Java
// 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:

Java
// 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:

Java
// 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
Which Layout to Use?

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:

Java
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:

Java
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:

Java
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:

Java
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.

Automatic vs. Custom

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:

Java
// 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:

Java
// 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:

Java
// 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):

Java
// 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:

Java
// 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
Java
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:

Java
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:

Java
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

Java — Clipboard, Storage, and Notifications
// 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");
Java — Screen Detection and Fullscreen
// 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);
Java — Speech, Vibration, and Sharing
// 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");
Java — Network Monitoring and Wake Lock
// 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);
Java — Server-Side Timers
// 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().

Java
@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
Communication Mode Matters

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:

Java
@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 this for 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:

Java
@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):

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:

  1. Loads the HTML file from the servlet context
  2. Parses it with Oorian's Html5Parser into a full element tree
  3. Copies attributes from the template's <html> element (e.g., lang, dir) to the page
  4. Extracts the <head> and <body> and sets them as the page's head and body
  5. Calls createHead() and createBody() 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:

Java
@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:

Java
@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:

Java
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:

Java
Html5Parser parser = new Html5Parser();

Element content = parser.parse(
    "<div class='card'>" +
    "  <h2>Welcome</h2>" +
    "  <p>Hello, world!</p>" +
    "</div>"
);

body.addElement(content);
When to Use Templates

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:

Java
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
Minimal Dependencies

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:

Java
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:

Java
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:

PROPERTIES
# 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():

Java
new LaunchPad()
    .setConfigFile("server.properties")
    .launch(MyApplication.class);
Programmatic Settings Take Priority

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:

Java
// 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:

Java
// 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:

Java
// 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]");
Reading Strings from JsonArray

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.

Java
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 as final)
  • toJsonValue() — serializes fields to a JsonObject

Once a class implements Jsonable, it integrates seamlessly with JsonObject and JsonArray:

Java
// 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();
Jsonable Best Practice

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:

Java
// 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
<?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:

Java
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:

Java
// 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:

Java
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:

Java
// 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:

Java
// 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");
XML Features

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 (&amp;, &lt;, &gt;, &quot;, &apos;).

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:

Java
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:

Java
@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:

Java
// 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:

Java
// 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:

Java
// 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:

Java
@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:

Java
@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:

Java
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():

Java
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:

Java
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:

Java
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:

Java
// 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:

Java
// Report-only mode — violations logged but not enforced
head.setContentSecurityPolicyReportOnly(csp);

// Enforcement mode — violations are blocked
head.setContentSecurityPolicy(csp);
Start with Report-Only

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. Use RawHtml only 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 ValidatedForm for server-side input validation on all user-submitted data
Always Validate on the Server

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.

↑ Table of Contents