Skip to main content

Java Runtime Extension Points

TeaQL Java projects customize runtime behavior through UserContext.

This page describes the Java runtime and Spring Boot starter extension points. Rust runtime extension points are documented separately under the Rust section.

In application configuration, a project can point TeaQL to its custom context class:

teaql.contextClass=<your context class>

For example:

teaql.contextClass=com.doublechaintech.cmes.CmesUserContext

The project context extends the generated or base context and becomes the convergence point for business runtime behavior.

What Belongs in UserContext

Use UserContext to centralize:

  • cache behavior;
  • global or distributed locks;
  • transaction helpers;
  • integration with middleware;
  • dynamic logging;
  • dynamic statistics;
  • observation and debugging;
  • tenant and permission policy;
  • localization and validation message behavior;
  • audit metadata;
  • read/write routing.

The goal is to avoid scattering runtime policies across controllers, repositories, and one-off service utilities.

Resource Convergence

Business services should not each invent their own way to access infrastructure resources. The context gives the project a consistent place to expose shared resources.

Controller / Service / Util
-> CustomUserContext
-> cache / lock / transaction / logging / audit / middleware

This keeps generated entity and query code framework-aware only through the runtime boundary.

Dynamic Debugging

Runtime extension points are also useful for diagnostics:

  • enable SQL logs for a request;
  • inspect dynamic statistics;
  • observe runtime behavior;
  • capture request-scoped state;
  • debug generated query execution.

Request Policy Enforcement

TeaQL requests are built separately from execution. A request can be passed around, composed, and returned from helper methods before it is finally executed through UserContext.

The last runtime hook before repository submission is:

protected <T extends Entity> SearchRequest<T> enforceRequestPolicy(SearchRequest<T> request) {
return request;
}

Use this method to enforce project-level request policy:

  • tenant or customer isolation;
  • role and team visibility;
  • region or data residency restrictions;
  • sensitive-read audit;
  • raw SQL restrictions;
  • default page-size limits;
  • protection against accidental unlimited scans.

Example:

public class MultiTalentUserContext extends MultiTalentGeneratedUserContext {

@Override
protected <T extends Entity> SearchRequest<T> enforceRequestPolicy(SearchRequest<T> request) {
request = super.enforceRequestPolicy(request);

if ("Candidate".equals(request.getTypeName())) {
request.appendSearchCriteria(
request.createBasicSearchCriteria(
"customerAccount",
Operator.EQUAL,
currentCustomerAccount()));
}

return request;
}
}

This keeps query code focused on business intent while the runtime protects infrastructure and customer data at the execution boundary.

For a longer case study, see Enforcing Request Policy at the Runtime Boundary.

Other Extension Points

enforceRequestPolicy is the main boundary for protecting data access. TeaQL also provides several smaller runtime hooks. Use them only for the lifecycle stage they describe.

Extension pointWhat it is forOverride or implement
Request policy enforcementAdd tenant filters, permission rules, page-size limits, raw SQL restrictions, or sensitive-read audit before a request reaches the repository.UserContext.enforceRequestPolicy(SearchRequest<T> request)
Before createFill create-time metadata, validate create-only rules, or audit a new entity before insert.UserContext.beforeCreate(EntityDescriptor descriptor, Entity entity)
Before updateCheck update permissions, inspect changed fields, fill update metadata, or audit an update before persistence.UserContext.beforeUpdate(EntityDescriptor descriptor, Entity entity)
Before deleteBlock unsafe deletes, require permission, or audit logical delete before persistence.UserContext.beforeDelete(EntityDescriptor descriptor, Entity entity)
Before recoverCheck permission and audit recovery of a logically deleted entity.UserContext.beforeRecover(EntityDescriptor descriptor, Entity entity)
After loadMask fields, attach request-scoped dynamic values, or observe sensitive reads after an entity is loaded.UserContext.afterLoad(EntityDescriptor descriptor, Entity entity)
After persistClear custom runtime state, dispatch follow-up work, or record post-save audit after create/update/delete/recover succeeds.UserContext.afterPersist(BaseEntity item)
Event dispatchSend domain events to an event bus, message queue, or project-specific observer.UserContext.sendEvent(Object event)
Error translationCustomize validation and checker messages for a project, language, or view model.UserContext.getNaturalLanguageTranslator(Entity entity)
Translation serviceRoute translation requests to a project translator or external translation service.UserContext.translate(TranslationRequest request)
Request initializationRead HTTP headers, trace IDs, operator identity, tenant, locale, or request body into the context.UserContextInitializer.init(UserContext userContext, Object request)
Generic save validationValidate entities submitted through generated base-service save endpoints.BaseService.validateEntityForSave(UserContext ctx, BaseEntity entity)
Generic delete validationValidate entities submitted through generated base-service delete endpoints.BaseService.validateEntityForDelete(UserContext ctx, Entity entity)
Generic before saveNormalize or enrich an entity handled by the generated base service before it is saved.BaseService.beforeSave(UserContext ctx, BaseEntity item)

Practical Rule

If a behavior is cross-cutting and project-specific, put it behind UserContext or a runtime extension rather than copying it into every controller or query method.