3 Using Jakarta MVC 3.0 in WebLogic Server Web Applications
Jakarta MVC 3.0 defines a standard way to build server-side web applications using the classic MVC architectural pattern. Jakarta MVC 3.0 is aligned with Jakarta EE 11 and Jakarta RESTful Web Services 4.0
The specification is based on Jakarta RESTful Web Services (JAX-RS), meaning controllers are implemented as JAX-RS resource classes, while views are typically rendered using technologies such as JSP or Facelets.
Note:
Jakarta MVC 3.0 requires a built-in JSP view engine. An implementation can support additional view engines, but Jakarta MVC 3.0 does not require Facelets support. Facelets is the standard view technology for Jakarta Faces and normally uses.xhtml files.
For more information about the Jakarta MVC 3.0 architecture, see the Jakarta MVC 3.0 specification.
Developing Jakarta MVC Applications
Jakarta MVC is a specification, not a complete application recipe. It defines the portable contracts for actions, models, views, binding, security, events, application configuration, view engines, locale resolution, and form-method override. Oracle WebLogic Server supplies the Jakarta MVC implementation and the Jakarta EE services on which it runs. This chapter connects those two perspectives: it explains the specification contract, then shows how a Web application packages and uses that contract on Oracle WebLogic Server.
The examples in this chapter use a small catalog application throughout. The application lists products, displays an edit form, validates submitted data, redirects after a successful update, and renders JSP views. Reusing one application matters: it lets you see how a model created by a controller becomes view data, how a form reaches that controller, and where cross-cutting behavior belongs.
Before You Begin
- Oracle WebLogic Server 26
- Jakarta EE 11 APIs
- a WAR application
<dependencies>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>11.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.mvc</groupId>
<artifactId>jakarta.mvc-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>Understanding Jakarta MVC Applications
Understanding the Jakarta MVC Web Application Model
Jakarta MVC is an action-based Web programming model. A browser requests an action; a controller obtains input and invokes application logic; the controller supplies a model and selects a view; the view creates the HTML that is returned to the browser. The model is deliberately the connection between controller and view. A controller should decide what data a user can see; a JSP should decide how that data is presented.
This is different from a conventional Jakarta RESTful Web Services endpoint. A REST resource commonly returns a representation such as JSON. An MVC controller normally returns a view name. The same Jakarta RESTful Web Services routing annotations select both kinds of method, but @Controller tells the MVC runtime to treat the action result as MVC behavior.
/catalog/mvc/products MVC action -> JSP page
/catalog/api/products/42 REST resource -> JSON representation Following an MVC Request Through the Application
${products}. Seeing that path first makes the later chapters on binding, CSRF, providers, and view engines easier to place.
- The Web application receives a request below its context root.
- The Jakarta RESTful Web Services application path and resource annotations select a controller action.
- CDI supplies the controller and its injected services for the request.
- The controller reads input, invokes the application service, and adds view data to
Modelsor a named CDI bean. - Jakarta MVC selects a view engine for the returned view name. For a JSP view, the engine forwards to the view and exposes model entries as request attributes.
- JSP and Expression Language render the response. The important boundary for application code is step 4. It should contain the application decision: whether the caller is authorized, whether the input is valid, which service operation to perform, and which view or redirect follows. The framework performs routing, contextual injection, view selection, and JSP dispatch according to its Jakarta EE and Jakarta MVC contracts.
Using the Jakarta MVC APIs and Jakarta EE Technologies
Jakarta MVC is composed from existing Jakarta EE technologies. Use their standard APIs instead of trying to reproduce framework behavior in a controller.
For example, a ProductController is not a special proprietary controller class. It is a CDI-managed Jakarta RESTful Web Services resource with the portable @Controller annotation. That is why it can inject CDI services and use familiar route annotations.
| Technology | Role in an MVC application | Typical application use |
|---|---|---|
| Jakarta RESTful Web Services | Matches URL, HTTP method, and request parameters. | @ApplicationPath, @Path, @GET, @POST, parameter annotations.
|
| CDI | Creates controllers and application beans. | @RequestScoped, injection, named model beans, lifecycle observers.
|
| Jakarta MVC | Connects actions, models, views, binding, CSRF, and URI generation. | @Controller, Models, MvcContext, BindingResult.
|
| Jakarta Servlet | Defines the WAR request, session, filter, and deployment model. | web.xml, sessions, application filters where needed.
|
| JSP and Expression Language | Renders the selected HTML view. | JSP, tag libraries, ${mvc...}, escaped output.
|
| Jakarta Bean Validation | Validates submitted form data. | @Valid, constraint annotations on a form bean.
|
Using Jakarta MVC on Oracle WebLogic Server
Oracle WebLogic Server 26 supplies Jakarta MVC 3.0. The default MVC implementation is Eclipse Krazo, which works with the server's Jakarta RESTful Web Services, CDI, Servlet, and JSP facilities. An application uses the Jakarta APIs; it does not need to call Eclipse Krazo implementation APIs to create a controller or render a JSP. The server-specific part of an MVC application is therefore mostly ordinary WebLogic Server Web-application work: package a WAR, choose a context root, deploy it to a target, and use server-provided APIs at run time. The MVC programming model remains the Jakarta MVC model. This guide calls out a WebLogic Server concern only when it affects a deployment decision, such as the server-supplied runtime or a weblogic.xml context root.
Planning the Application Archive
catalog.war
├── WEB-INF/
│ ├── beans.xml
│ ├── classes/com/example/catalog/
│ │ ├── CatalogApplication.class
│ │ ├── ProductController.class
│ │ └── ProductService.class
│ ├── lib/ application-owned libraries
│ ├── views/products/list.jsp
│ ├── web.xml optional standard configuration
│ └── weblogic.xml optional WebLogic Server configuration
└── css/ and images/ static resources
Put views below WEB-INF/views when using the default JSP view folder. A browser cannot retrieve content below WEB-INF directly. That structural protection means that the controller remains the entry point that supplies the model and applies application authorization. beans.xml is not an MVC switch. It is a CDI bean-archive descriptor. Include it when the application needs to select CDI discovery behavior or make that behavior explicit. Similarly, add weblogic.xml only for a WebLogic Server setting the application actually requires, such as a fixed context root.
Important Development and Security Considerations
- Give the Jakarta RESTful Web Services application a nonempty application path, such as
mvc, and keep page and API routes distinct. - Use request-scoped controllers unless a different CDI lifecycle is needed and understood.
- Keep controller actions small: obtain input, call an application service, prepare view data, then choose a view or redirect.
- Treat models as data for the selected view. Do not use a model as a substitute for a user session or a service cache.
- Escape untrusted values in the output context in which they are rendered.
- Protect state-changing actions with Web application authorization and Jakarta MVC CSRF protection; hiding a link or a JSP is not authorization.
- Test the deployed URL, not only a controller unit test. The deployment test verifies the application path, CDI discovery, view location, and JSP rendering together.
The next topic, Using Models, Views, and Controllers turns this model into controllers, model data, views, URI generation, and redirects.
Using Models, Views, and Controllers
Choosing Controller Actions
An MVC controller is a Jakarta RESTful Web Services resource method annotated with @Controller. Put @Controller on a class when all of its resource methods return HTML views. Put it on individual methods when the class also contains a conventional REST endpoint. The annotation is the bridge between REST routing and MVC view processing; it does not replace @Path or an HTTP method annotation.
The catalog list action illustrates the division of responsibility. Routing selects list; the controller asks the service for products; Models makes the result available to the view; the returned string names the view.
@Controller
@RequestScoped
@Path("products")
public class ProductController {
@Inject ProductService productService;
@Inject Models models;
@GET
public String list() {
models.put("products", productService.findAll());
return "products/list.jsp";
}
@GET
@Path("{id}")
public String detail(@PathParam("id") long id) {
models.put("product", productService.find(id));
return "products/detail.jsp";
}
}
Use a CDI scope for a controller. @RequestScoped suits the common case: the controller represents one request and does not retain mutable state after the response. CDI injection, interceptors, and decorators are then available in the normal way. Do not create a controller with new; that bypasses CDI and creates a different lifecycle from the one the application declares.
Selecting a View or Response
Jakarta MVC defines three common ways for an action to select a view. Choose the form that communicates the action's intent rather than treating every action as a string-returning method.
| Action result | Use it when | Example |
|---|---|---|
String |
The view is chosen at run time. | Return "products/detail.jsp".
|
void with @View |
The action always has one view. | A help page with no decision to make. |
Response
|
Status, headers, media type, or redirect metadata matter. | A 303 See Other after an update.
|
@View also provides the default view if a non-void action returns null. That makes an explicit fallback readable when a controller has a small conditional decision:
@GET
@Path("help")
@View("help.jsp")
public void help() {
}
@GET
@Path("account")
@View("account-not-found.jsp")
public String account(@QueryParam("id") String id) {
return accountService.exists(id) ? "account.jsp" : null;
}Returning a Response does not turn an MVC action into an API endpoint. Its status and headers remain useful when the entity is a view name. Use it, for example, when a page must explicitly prevent caching. Use an ordinary non-controller resource method when the response body itself is JSON or another representation.
Preparing View Data
Models is a request-oriented map for data that a controller has selected for the next view. The controller and JSP agree on a name such as products; the JSP does not need to know how the service obtained the list. Before the JSP is rendered, MVC exposes model entries as request attributes, so ${products} is available to Expression Language.
Use a named CDI bean when presentation data has a useful type and lifecycle. For example, a multi-step display may be clearer as CatalogModel than a set of unrelated string keys. The decision is an application design choice; both approaches remain view data, not a replacement for persistent user state.
@Named("catalog")
@RequestScoped
public class CatalogModel {
private List<Product> products;
public List<Product> getProducts() { return products; }
public void setProducts(List<Product> products) { this.products = products; }
}Do not place secrets, authorization decisions, or unfiltered user input in a model merely because a JSP can read it. Select the minimal data required by the authorized view and encode it correctly when it is displayed.
Rendering JSP Views
products/list.jsp identifies WEB-INF/views/products/list.jsp. The controller returns a logical view name; the JSP handles HTML markup and iteration.<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<h1>Products</h1>
<ul>
<c:forEach var="product" items="${products}">
<li><c:out value="${product.name}" /></li>
</c:forEach>
</ul><c:out> is appropriate for text in an HTML element. Other output contexts need their own encoding rules: an HTML attribute, a URL, JavaScript, and CSS are not interchangeable. Keep database access, authorization checks, and state changes out of a JSP so the same controller rules apply regardless of which view is selected.
Building Application URIs
MvcContext, exposed in a JSP as mvc, can build a URI from a controller method. Give routes that are referenced from views a stable @UriRef so a Java class rename does not silently change the view contract.@GET
@Path("{id}")
@UriRef("product-detail")
public String detail(@PathParam("id") long id) {
// prepare the model
return "products/detail.jsp";
}
<a href="${mvc.uri('product-detail', {'id': product.id})}">
<c:out value="${product.name}" />
</a>The parameter-map keys must match the route's path, query, or matrix parameter names. Test generated links after changing a route, especially when the application is deployed under a non-default context root or fronted by a proxy.
Redirecting After an Action
redirect: when a simple redirect is enough, or return Response.seeOther when the response metadata is part of the action's intent.
@Inject
private BindingResult bindingResult;
@POST
public String create(@BeanParam @Valid ProductForm form) {
if (bindingResult.isFailed()) {
return "products/edit.jsp";
}
Product product = productService.create(form);
return "redirect:products/" + product.getId();
}@RedirectScoped is for the narrow case where a CDI bean must survive the redirect and the immediately following request, such as a one-time success message. It is not a general session scope. Test the complete browser flow: submit the form, observe the redirect status and Location header, follow the GET, then refresh the GET to confirm that the original operation is not run again.
Using Data Binding in Jakarta MVC Applications
This topic includes the following sections:
- Understanding the Form Processing Contract
- Designing a Form Bean
- Retaining Binding Failures in the Controller
- Rendering Field Errors
- Converting Values Reliably
- Testing a Form Action
Understanding the Form Processing Contract
An HTML form contains strings. An application action usually needs typed values: a price, quantity, date, identifier, or domain object. Jakarta RESTful Web Services performs parameter binding and Jakarta Bean Validation can validate the resulting object. Without MVC binding support, a conversion or validation failure can prevent the resource method from running, which is a reasonable behavior for an API but not a helpful browser form experience.
@MvcBinding changes that experience. It tells Jakarta MVC to invoke the controller and expose the failure through BindingResult, allowing the action to return the same JSP with field values and messages. The controller still must check the result before it calls a state-changing service.
Designing a Form Bean
@BeanParam class. Put syntactic rules on the form at the boundary—required fields, range, length, and format—not in the JSP. The service then receives a form that has passed the controller's validation decision.
public class ProductForm {
@FormParam("name")
@MvcBinding
@NotBlank(message = "Enter a product name")
@Size(max = 80)
private String name;
@FormParam("price")
@MvcBinding
@NotNull(message = "Enter a price")
@DecimalMin(value = "0.01")
private BigDecimal price;
public String getName() { return name; }
public BigDecimal getPrice() { return price; }
}The form bean describes input; it need not be the same class as an entity or the object rendered by a JSP. Keeping those concepts distinct prevents clients from binding fields that should not be editable, such as an owner or an authorization flag.
Retaining Binding Failures in the Controller
Inject BindingResult alongside the form. A failed conversion—for example, abc in a BigDecimal field—and a violated Bean Validation constraint both appear in the binding result. The action selects the edit view before calling the service when either one occurs.
@Inject
private BindingResult bindingResult;
@POST
@Path("{id}")
public String update(@PathParam("id") long id,
@BeanParam @Valid ProductForm form) {
if (bindingResult.isFailed()) {
models.put("productId", id);
return "products/edit.jsp";
}
productService.update(id, form);
return "redirect:products/" + id;
}The WebLogic Server deployment does not require special form-binding configuration for this portable MVC pattern. The application supplies the controller, form bean, constraints, and JSP; the server-provided MVC runtime participates in the Web application's normal request processing.
Rendering Field Errors
A form view needs two kinds of information after a failed submission: the value the user attempted to enter and the reason it is unacceptable. Keep the JSP focused on displaying those values. It should not decide whether an update is allowed or attempt its own numeric conversion.
@Inject
private BindingResult bindingResult;
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<label for="price">Price</label>
<input id="price" name="price" value="<c:out value='${form.price}'/>" />
<c:forEach items="${bindingResult.getErrors('price')}" var="error">
<p id="price-error" class="error"><c:out value="${error.message}" /></p>
</c:forEach>
Add aria-describedby="price-error" to the price input when the error element is rendered. BindingResult has isFailed() without a parameter, getErrors(String), and getAllMessages() without a parameter.
Use the binding-result API supported by the selected view technology to render messages. Give error text an accessible association with the corresponding input, and preserve only safe, expected values. Passwords and other secrets should not be copied back into a page merely to repopulate a form.
Converting Values Reliably
Use typed form properties where a field has a clear type. MVC conversion covers primitives, wrapper types, numeric values, booleans, and other types for which an applicable converter exists. Locale can influence human-entered numeric and date values; use the MVC request locale consistently for parsing, formatting, and error messages.
For a domain-specific value, make the conversion rule explicit and test it. Do not accept an arbitrary identifier just because it can be parsed. A SKU, currency code, or account identifier may need an allowlist or a service lookup after conversion succeeds.
Testing a Form Action
Test the deployed form as a request sequence, not just as a Java method. At a minimum, verify a valid submission, a missing required value, an invalid typed value, an out-of-range value, and a failure that must not call the service. For each invalid case, assert that the response renders the expected form and field message. For the valid case, assert the redirect and follow it to the GET view. Add CSRF and authorization cases from the security chapter, because they protect the same state-changing action.
Securing Jakarta MVC Applications
This chapter includes the following sections:
- Securing the Action, Not Just the Page
- Using Implicit CSRF Protection
- Selecting Explicit CSRF Protection
- Encoding View Output
- Handling Security Failures
- Verifying a Protected Form
Securing the Action, Not Just the Page
An MVC application is a Web application. Apply Oracle WebLogic Server Web application authentication and authorization to the URLs that perform actions, then apply business authorization in the service layer when an operation also depends on ownership or other domain data. A hidden button, an unlinked JSP, or a disabled browser field is not an authorization rule; a client can still send the request.
Treat each state-changing controller action as the security boundary. The catalog update action, for example, needs an authorized caller, a valid CSRF token, valid form data, and an application-level decision that the caller can change that product. These checks solve different problems and none replaces another.
Using Implicit CSRF Protection
Jakarta MVC 3.0 uses implicit CSRF protection by default. In implicit mode, the MVC runtime protects controller actions as defined by the specification. The view obtains the token field name and value from the MVC Expression Language object and submits them with the form.
<form method="post" action="${mvc.uri('product-update', {'id': product.id})}">
<input type="hidden" name="${mvc.csrf.name}" value="${mvc.csrf.token}" />
<label for="name">Name</label>
<input id="name" name="name" value="<c:out value='${product.name}'/>" />
<button type="submit">Save</button>
</form>The token belongs in every application form that submits a protected action, including forms that use method override. Generate it in the view instead of inventing a field name or copying a value between users. If a JavaScript client submits a request rather than a form, configure and use the MVC CSRF header name consistently with that client.
Selecting Explicit CSRF Protection
Use the standard CSRF property to select explicit protection only when the application has a clear reason to identify protected actions individually. In that mode, mark the relevant controller action with @CsrfProtected.
@POST
@Path("{id}")
@CsrfProtected
@UriRef("product-update")
public String update(@PathParam("id") long id,
@BeanParam @Valid ProductForm form) {
// Check binding, authorize the operation, update, and redirect.
return "redirect:products/" + id;
}Explicit mode trades the broad default for a maintenance obligation: every new state-changing action must be reviewed and marked. Document why that tradeoff is appropriate and add a security test that fails if an unmarked action is introduced. Do not disable CSRF protection merely because an action also uses authentication or role constraints.
Encoding View Output
CSRF protects a request from being forged; it does not make returned content safe to render. Encode values for their output context. In a JSP HTML text node, use <c:out> or an equivalent HTML-text encoder. For an attribute, URL, JavaScript, or CSS context, use the encoder appropriate to that context.
<p><c:out value="${product.description}" /></p>MVC also exposes encoders through MvcContext for views or application code that need the portable API. Keep the choice near the sink: a string encoded for an HTML element is not automatically safe in a script or URL. Do not solve output encoding by stripping arbitrary characters at input time, because the same value can be valid in one output context and unsafe in another.
Handling Security Failures
Plan the user experience for authentication, authorization, CSRF, binding, and application failures separately. A missing or invalid CSRF token is a request validation failure, not a reason to display the submitted data back to the caller. A role failure should use the Web application's configured access control behavior. An invalid form value can normally return the edit page with field messages.
Exception mappers and filters can be useful for a consistent error response, but they must not turn a security failure into a successful-looking page or leak request details. Preserve the original status and log enough server-side context for diagnosis without recording passwords, CSRF tokens, or other sensitive form values.
Verifying a Protected Form
Run a browser-level or HTTP-client test for each protected form. First request the form and retain the session state. Then verify a valid token succeeds, a missing token fails, an altered token fails, an unauthenticated caller is handled as configured, and an unauthorized authenticated caller cannot perform the action. Repeat the test for any form-method-override action. Deploy behind the production proxy path when relevant so HTTPS and session-cookie behavior are part of the evidence, not an assumption.
Using Events in Jakarta MVC Applications
This topic includes the following sections:
- Understanding MVC Lifecycle Events
- Choosing an Appropriate Observer
- Observing Controller and View Processing
- Observing Redirects
- Keeping Observers Safe and Fast
Understanding MVC Lifecycle Events
Jakarta MVC publishes CDI events around controller execution, view rendering, and redirects. Events are useful when the same observation applies to many actions: a request metric, audit correlation, or a diagnostic marker. They are not a second controller mechanism. The controller still owns application decisions and the selected view.
MVC events are synchronous CDI events. An observer runs on the request path, which means it can see request context but can also add latency or fail the same request if it performs expensive work.
Choosing an Appropriate Observer
Choose the event closest to the fact being observed. A before-controller event answers whether an action is about to run; an after-controller event is useful for the completed action; view events distinguish action selection from actual rendering; redirect events observe the destination selected by an action.
Keep event observers separate from authorization and business logic. If an operation must be authorized, the controller or service must enforce it even when no observer is present.
Observing Controller and View Processing
The following observer records a small metric tag after a controller action. It reads metadata and delegates only a bounded local operation. The metric system, not the observer, should handle batching or remote export.
@ApplicationScoped
public class MvcMetrics {
public void afterController(@Observes AfterControllerEvent event) {
metrics.increment("mvc.controller.completed",
event.getResourceInfo().getResourceMethod().getName());
}
}Use the event API to obtain the information the event defines. Do not parse a view name or redirect location to reconstruct routing decisions that the controller can express directly with URI APIs.
Observing Redirects
Redirect events are useful for diagnostics and audit metadata after the controller has chosen a safe destination. Do not rewrite the destination in an observer: redirect construction belongs in the controller, where it can use trusted route references and application authorization information.
Keeping Observers Safe and Fast
Do not perform remote calls, database queries, blocking I/O, or expensive aggregation directly in an MVC event observer. Record the minimal information needed and use an application-managed asynchronous design for later work. Test that an observer failure does not hide the original controller or JSP failure, and test the relevant event sequence with an actual deployed action.
Configuring Jakarta MVC Applications
This topic includes the following sections:
- Defining the MVC Application Boundary
- Obtaining Request MVC Context
- Configuring Portable MVC Properties
- Using Providers with MVC
- Understanding Annotation Inheritance
- Configuring the WebLogic Server Context Root
Defining the MVC Application Boundary
An MVC controller is a Jakarta RESTful Web Services resource in an application. The application class establishes the URL space in which resource paths are matched and is the portable place to set MVC properties or explicitly register application classes. A nonempty application path keeps that URL space distinct from the Web application's context root and from JSP resources.
@ApplicationPath("mvc")
public class CatalogApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
return Set.of(ProductController.class, AccountController.class);
}
}With a context root of /catalog, @ApplicationPath("mvc"), and @Path("products"), the product action is /catalog/mvc/products. This separation also means the view folder can stay under WEB-INF, outside the URLs that browser clients request.
Use the standard application mechanism that matches the rest of the Web application. An Application subclass is a simple choice when annotations and properties are sufficient. A ResourceConfig-based application can be useful for an application that already uses Jersey's programmatic registration model. In either case, application code should register its own resources and extensions through supported application APIs, rather than depend on MVC implementation bootstrap classes.
Obtaining Request MVC Context
MvcContext represents MVC information for the current request: the base path, locale, URI builder, CSRF helper, and encoders. Inject it into a controller when the controller needs that information; JSPs access the corresponding object as mvc in Expression Language.
@Inject MvcContext mvc;
@Inject Models models;
@GET
@Path("status")
public String status() {
models.put("basePath", mvc.getBasePath());
models.put("locale", mvc.getLocale());
return "status.jsp";
}The context is request-specific. Copy a needed value into a view model or another defined state mechanism; do not retain MvcContext in a session- or application-scoped object for later requests.
Configuring Portable MVC Properties
Jakarta MVC defines properties for view selection and CSRF behavior. Set them on the application class so the configuration lives next to the routes it affects. The following catalog example moves JSPs to a named view folder and keeps the specification's implicit CSRF mode while selecting a header name for browser clients that use header submission.
@ApplicationPath("mvc")
public class CatalogApplication extends Application {
@Override
public Map<String, Object> getProperties() {
Map<String, Object> properties = new HashMap<>();
properties.put(ViewEngine.VIEW_FOLDER, "/WEB-INF/catalog-views/");
properties.put(Csrf.CSRF_PROTECTION, Csrf.CsrfOptions.IMPLICIT);
properties.put(Csrf.CSRF_HEADER_NAME, "X-Catalog-CSRF");
return properties;
}
}Changing a property changes an application-level behavior, not merely a JSP detail. Keep a custom view folder below WEB-INF; test every protected form when CSRF configuration changes; and make the configured header name part of the client-side request contract. The implementation reference lists the standard property names and defaults.
Using Providers with MVC
Jakarta MVC operates inside the Jakarta RESTful Web Services pipeline. That is how a matched controller action can be recognized as MVC, how a returned view name reaches a view engine, and how ordinary JAX-RS extensions can still take part in the same request. Eclipse Krazo, the default implementation, uses that extension pipeline internally; those implementation providers are not an application API.
The application-facing consequence is simpler. An application can use standard JAX-RS providers—filters, interceptors, exception mappers, message-body providers, and features—with MVC routes just as it can with other resources. Use a provider for a cross-cutting HTTP concern. Do not put an authorization policy, a database update, or view-selection logic into a filter merely because it runs before or after an action.
Annotate a provider with @Provider for provider discovery, or register the application's provider explicitly through its application configuration. Add @Priority only when the relative position is meaningful to the application.
@Provider
@Priority(Priorities.HEADER_DECORATOR)
public class CacheControlResponseFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext request,
ContainerResponseContext response) {
if (request.getUriInfo().getPath().startsWith("products/")) {
response.getHeaders().putSingle("Cache-Control", "no-store");
response.getHeaders().putSingle("X-Content-Type-Options", "nosniff");
}
}
}For request filters and read/write interceptors, lower priority numbers run first. Response filters run in reverse priority order. The order of components with the same priority is implementation-defined. Use a standard Priorities constant when it expresses the concern, and test the provider with the MVC action, form, redirect, or JSP response it changes. This is especially important for a provider that reads or changes headers, entities, or exceptions.
Understanding Annotation Inheritance
MVC annotations follow the Jakarta RESTful Web Services annotation-inheritance rules. A resource method can inherit relevant annotations from a superclass or interface only when the implementation method does not declare an annotation of the same kind. Superclass annotations take precedence over interface annotations.
This permits a reusable route contract, but it can hide the route or MVC behavior of a controller. Prefer an explicit annotation on an action when a reader would otherwise have to inspect several interfaces to understand its HTTP method, path, or view behavior. Test inherited routes after refactoring; annotation inheritance is a route contract, not just a Java style choice.
Configuring the WebLogic Server Context Root
The context root is the WebLogic Server deployment portion of the URL. Use the deployment configuration when its value is environment-specific. When the WAR must declare a fixed context root, use WEB-INF/weblogic.xml:
<weblogic-web-app xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app">
<context-root>/catalog</context-root>
</weblogic-web-app>Do not add a WebLogic Server descriptor solely to enable MVC. MVC routes, models, views, binding, and providers are configured through their Jakarta APIs. After deployment, test the final URL with the selected context root and application path, since a successful unit test cannot verify that URL composition.
Additional Jakarta MVC Capabilities
This section includes the following topics:
Using View Engines in Jakarta MVC Applications
This topic includes the following sections:
- Understanding View Selection
- Using the Built-In JSP View Engine
- Changing the View Folder Convention
- Providing a Custom View Engine
- Diagnosing a View Failure
Understanding View Selection
A controller's view name is an instruction to the MVC runtime, not a file read performed by the controller. MVC asks available ViewEngine beans which ones support that name, then uses the selected engine to render it. This keeps the controller independent of the template technology: the controller chooses products/list.jsp; the JSP engine owns JSP rendering.
Note:
Jakarta MVC 3.0 requires a built-in JSP view engine. An implementation can support additional view engines, but Jakarta MVC 3.0 does not require Facelets support. Facelets is the standard view technology for Jakarta Faces and normally uses.xhtml files.
Oracle WebLogic Server supplies the required JSP view-engine support with its Jakarta MVC runtime. Use it for ordinary server-rendered HTML. A custom engine is an application extension for a genuinely different view technology, not a replacement for the server's MVC or JSP libraries.
Using the Built-In JSP View Engine
By default, a relative JSP name is resolved below /WEB-INF/views/. The catalog action can therefore return products/summary.jsp, while the archive contains WEB-INF/views/products/summary.jsp. The location protects the view from direct browser access and keeps the controller as the action entry point.
@GET
@Path("summary")
public String summary() {
models.put("orders", orderService.recentOrders());
return "orders/summary.jsp";
}The JSP receives Models entries as request attributes. It should render those values using JSP, Expression Language, and tag libraries; it should not open a database connection or repeat the controller's access-control decision.
Changing the View Folder Convention
An application can set ViewEngine.VIEW_FOLDER when its existing archive uses a different protected template root. The property affects every relative view name, so choose one convention and use it consistently.
@Override
public Map<String, Object> getProperties() {
return Map.of(ViewEngine.VIEW_FOLDER, "/WEB-INF/pages/");
}With that property, return "orders/summary.jsp" identifies WEB-INF/pages/orders/summary.jsp. Keep the root below WEB-INF unless direct access to templates is an intentional, separately protected design. If the application omits extensions through the standard view-extension property, document the convention and test every controller route after the change.
Providing a Custom View Engine
Provide a custom engine only when the application has a view type that JSP cannot serve. A custom engine is a CDI bean that implements ViewEngine. supports declares which view names it owns; processView writes the response for a selected name. The engine should have a clear suffix or other unambiguous selection rule so it cannot accidentally claim JSP views.
@ApplicationScoped
@Priority(ViewEngine.PRIORITY_APPLICATION)
public class TextViewEngine implements ViewEngine {
@Override
public boolean supports(String view) {
return view.endsWith(".txt");
}
@Override
public void processView(ViewEngineContext context)
throws ViewEngineException {
try {
context.getOutputStream().write(
String.valueOf(context.getModels().get("message"))
.getBytes(StandardCharsets.UTF_8));
} catch (IOException exception) {
throw new ViewEngineException(exception);
}
}
}MVC orders built-in, framework, and application view engines using the defined priorities. An application engine normally uses ViewEngine.PRIORITY_APPLICATION. If two engines support the same name at the same priority, selection is not a useful application contract. Fix the suffixes or priorities rather than relying on incidental order.
Package the custom engine and its application-owned dependencies in the WAR. It can use normal CDI injection, but it must be safe for concurrent requests when it is application scoped and must convert failures into a meaningful view exception.
Diagnosing a View Failure
Start at the controller and move outward: confirm the selected action and returned view name; confirm the configured view folder; inspect the deployed WAR for the expected path; then inspect JSP compilation or the custom engine's failure. For a custom engine, also confirm CDI discovery, supports behavior, and non-conflicting priority. Preserve the original exception in logs rather than replacing it with a generic error view that hides the cause.
Internationalizing Jakarta MVC Applications
This topic includes the following sections:
- Understanding the Request Locale
- Using the Default Locale Resolution
- Supplying an Application Locale Policy
- Localizing Views and Forms
- Testing Locale Behavior
Understanding the Request Locale
An MVC request has one resolved locale. MvcContext exposes it to a controller, and a JSP can use the MVC context while it renders the resulting view. Treat that locale as the presentation locale for the request: use it for message selection and display formatting, and keep it aligned with any human-entered values that MVC converts.
@Inject MvcContext mvc;
@GET
public String show() {
models.put("locale", mvc.getLocale());
return "locale.jsp";
}This does not make every business value locale-specific. Persist money, quantities, and dates in their appropriate typed forms; format them for the view, and parse user input at the controller boundary.
Using the Default Locale Resolution
The default MVC locale resolver uses the request's Accept-Language header and falls back to the JVM default locale when it cannot select an applicable locale. This is a good starting point for a browser-facing application, but a production multilingual application should decide whether the JVM fallback is acceptable for every managed server.
Use the same expected locale in a controller and JSP; do not independently interpret headers in both places. That avoids one locale formatting a price while another locale selects the validation message.
Supplying an Application Locale Policy
Provide a CDI LocaleResolver when the application's policy is based on a signed-in user's preference, a trusted tenant configuration, or another application source. Give it an explicit priority above the default resolver when it should answer first, and return null when it has no applicable answer so the normal fallback can continue.
@ApplicationScoped
@Priority(100)
public class AccountLocaleResolver implements LocaleResolver {
@Override
@Inject
private AccountLocale accountLocale;
public Locale resolveLocale(LocaleResolverContext context) {
String preferred = accountLocale.findForCurrentUser();
return switch (preferred) {
case "fr" -> Locale.FRENCH;
case "en" -> Locale.ENGLISH;
default -> null;
};
}
}Validate any request-selected language against an application allowlist. A locale resolver establishes presentation behavior for the whole request, so it should be fast, deterministic, and independent of an untrusted arbitrary locale value.
Localizing Views and Forms
Use the application's resource-bundle mechanism for labels and validation messages. Format a display value with the request locale immediately before rendering it, rather than passing a locale-formatted string to business logic.
NumberFormat formatter = NumberFormat.getCurrencyInstance(mvc.getLocale());
models.put("displayPrice", formatter.format(product.getPrice()));For a form, test the exact input syntax that users see. A comma decimal separator, month order, or translated validation message may be correct in one locale and wrong in another. Keep the form field's type and constraint stable; only its human presentation and parsing are locale-sensitive.
Testing Locale Behavior
Test the default resolver with representative Accept-Language values, including quality values and a missing header. Test a custom resolver's priority and null fallback. For each supported locale, test at least one rendered message, one formatted value, and one submitted value that requires conversion. In a clustered deployment, verify that the chosen locale remains consistent after a new request reaches another managed server; persist a user preference when request headers alone cannot express the policy.
Using Form Method Override in Jakarta MVC Applications
This topic includes the following sections:
- Understanding the HTML Form Limitation
- Creating an Overridden Action
- Configuring the Override Field
- Securing an Overridden Request
- Testing the Request Path
Understanding the HTML Form Limitation
HTML forms directly submit GET or POST. Jakarta MVC can overwrite the method of a POST request when the request contains the configured hidden field. The default behavior is enabled and uses _method. This lets an application use a route such as @PUT @Path("{id}") without creating a second POST-only route solely for the browser.
Method override changes routing, not authorization. The browser still sends a POST, and the application must still protect the resulting state-changing action with normal Web application authorization, CSRF validation, binding, and business checks.
Creating an Overridden Action
The edit page posts a hidden method value and CSRF token. MVC processes the form according to the configured override behavior before selecting the controller action.
<form method="post" action="${mvc.uri('product-update', {'id': product.id})}">
<input type="hidden" name="_method" value="PUT" />
<input type="hidden" name="${mvc.csrf.name}" value="${mvc.csrf.token}" />
<label for="name">Name</label>
<input id="name" name="name" value="<c:out value='${product.name}'/>" />
<button type="submit">Update</button>
</form>
@Inject
private BindingResult bindingResult;
@PUT
@Path("{id}")
@UriRef("product-update")
public String update(@PathParam("id") long id,
@BeanParam @Valid ProductForm form) {
if (bindingResult.isFailed()) {
return "products/edit.jsp";
}
productService.update(id, form);
return "redirect:products/" + id;
}Only a POST form with the configured field participates in this feature. Do not assume that a GET request or an arbitrary request header has the same effect. When a JavaScript client can send a real PUT or PATCH request, prefer the normal HTTP method unless HTML-form compatibility is the reason for the override.
Configuring the Override Field
Set the portable form-method-overwriter properties on the application class when an application needs to disable the feature or use a different field name.
@Override
public Map<String, Object> getProperties() {
return Map.of(
FormMethodOverwriter.FORM_METHOD_OVERWRITE,
FormMethodOverwriter.Options.ENABLED,
FormMethodOverwriter.HIDDEN_FIELD_NAME, "_httpMethod");
}Changing the name is a client contract change: update every JSP, automated test, and any non-browser client. Disable method override when the application does not use it rather than leaving a request transformation enabled without a purpose.
Securing an Overridden Request
Include the same CSRF token and validation logic that a direct POST action would use. A hidden field is client-controlled input and cannot authorize an operation. Test a valid overridden form, a missing override field, a non-POST request, an invalid method value, an invalid CSRF token, and an unauthorized caller. Assert both the matched route and the outcome: an update must not be performed when any of those preconditions fails.
Testing the Request Path
- A POST request containing the configured override field and
PUTvalue reaches the@PUTcontroller action. - A POST request without the override field remains a POST request.
- A non-POST request is not overwritten.
- An unsupported method value is rejected and does not update application data.
- A missing or invalid CSRF token is rejected.
- An unauthorized caller cannot perform the update.
For every rejected request, verify that the service method that changes application data is not called.
Jakarta MVC Implementation Reference for Oracle WebLogic Server
Review the following sections.
Implementation Boundary
Oracle WebLogic Server 26 supplies Jakarta MVC 3.0 with Eclipse Krazo as the default MVC implementation. Applications compile against the Jakarta EE and Jakarta MVC APIs and use the standard controller, CDI, provider, view-engine, and configuration contracts. The implementation name explains the server runtime supplied for MVC; it is not an application dependency or a proprietary controller API.
Package application code, templates, descriptors, and application-owned libraries in the WAR. Keep the MVC API at provided scope in the application build and avoid using the archive to select a different MVC implementation.
Specification-to-Application Map
| Jakarta MVC 3.0 area | Application task on Oracle WebLogic Server | Reference Link |
|---|---|---|
|
Controllers and controller instances |
Use CDI-managed REST resource methods with |
|
|
Models, views, URI building, redirects |
Put request display data in |
|
|
Binding and conversion |
Use a |
|
|
Security |
Configure Web application authorization and use MVC CSRF and output encoding. |
|
|
Events |
Observe lifecycle facts with short, synchronous CDI observers. |
|
|
Applications, context, properties, providers |
Define the application path, use |
|
|
View engines |
Use JSP by default or supply an unambiguous CDI |
|
|
Internationalization |
Use the MVC request locale and, where needed, a CDI |
|
|
Form method override |
Use a POST form plus the configured hidden field for a PUT, PATCH, or DELETE action. |
Common Annotations
| Annotation | Purpose |
|---|---|
| @Controller | Identifies a Jakarta RESTful Web Services resource method or class as MVC. |
| @View | Selects the view for a void action or the default when a non-void action returns null.
|
| @MvcBinding | Lets an action receive binding and validation failures through BindingResult.
|
| @CsrfProtected | Marks an action for CSRF validation in explicit CSRF mode. |
| @RedirectScoped | Makes a CDI bean available across a redirect and the immediately following request. |
| @UriRef | Assigns a stable logical route reference for URI generation. |
| @Provider | Makes an application JAX-RS provider eligible for provider discovery. |
Standard Properties
Set these properties through the Jakarta RESTful Web Services application configuration mechanism. Treat a changed security, view-folder, or client-header property as an application behavior change and cover it with a deployed integration test.
| Constant | Default or Use |
|---|---|
| ViewEngine.VIEW_FOLDER | The default JSP view folder is /WEB-INF/views/; set the property for another protected root. |
| ViewEngine.VIEW_EXTENSION | Establishes a default view extension when the application uses extension-less names. |
| Csrf.CSRF_PROTECTION | Jakarta MVC 3.0 defaults to implicit CSRF protection. |
| Csrf.CSRF_HEADER_NAME | Names the request header used when a client submits a CSRF token by header. |
| FormMethodOverwriter.FORM_METHOD_OVERWRITE | Form method override is enabled by default. |
| FormMethodOverwriter.HIDDEN_FIELD_NAME | The default hidden field is _method.
|
Archive Checklist
Before delivery, verify one end-to-end route for each feature used by the application: matching and JSP rendering, valid and invalid form submission, CSRF behavior, authorization, redirects, generated URI, locale, any custom view engine, and any provider that affects a response. This provides evidence for the deployed application rather than assuming that a unit test of a controller covers the Web container and MVC integration.
WEB-INF/
├── beans.xml CDI discovery configuration when needed
├── classes/ application controllers, forms, and services
├── lib/ application-owned libraries
├── views/ JSP views, normally below this protected root
├── web.xml optional standard Web configuration
└── weblogic.xml optional WebLogic Server deployment settings