Skip to content
PYD ENTERPRISES

Java EE, EJB, Struts & Spring XML

Untangle monolithic Java EE, EJB and Struts codebases.

Session beans, entity beans, Struts actions, Spring XML wiring and SOAP contracts, parsed with their deployment descriptors so the call graph shows what the container actually does.

EJB INVENTORY · policy-admin.earEJB 2.1 · 47 beans
BeanKindTxRemote calls
RatingServiceBeanRemote + HomeStatelessSupports0
PolicyServiceBeanRemote + HomeStatelessRequired4
ClaimServiceBeanLocal + LocalHomeStatelessRequired3
QuoteCartBeanRemote + HomeStatefulRequired2
PolicyEJBLocalEntity (CMP 2.x)Mandatory0

QuoteCartBean holds conversational state · flagged for redesign

Enterprise Java parsing

Code and descriptors, read together.

In Java EE the behavior lives half in classes and half in XML. Parsing one without the other gets transactions, security and wiring wrong.

EJB 2.x / 3.x

Session beans resolved through Home, JNDI and the container.

The lookup of java:comp/env/ejb/RatingService is resolved through the ejb-ref in the deployment descriptor to RatingServiceBean, so the call graph crosses the remote boundary. The surcharge on more than two prior claims becomes rule POL-0213 with its rounding (half-up, two places). The container-managed transaction attribute becomes part of the operation's contract.

PolicyServiceBean.java (EJB 2.1)Java
1public class PolicyServiceBean implements SessionBean {
2 private SessionContext ctx;
3
4 public PolicyQuote quote(QuoteRequest req) throws RatingException {
5 try {
6 Object ref = new InitialContext().lookup("java:comp/env/ejb/RatingService");
7 RatingServiceHome home = (RatingServiceHome) PortableRemoteObject.narrow(ref, RatingServiceHome.class);
8 BigDecimal base = home.create().basePremium(req.getState(), req.getClassCode(), req.getLimit());
9 if (req.getPriorClaims() > 2) {
10 base = base.multiply(new BigDecimal("1.25"));
11 }
12 return new PolicyQuote(base.setScale(2, BigDecimal.ROUND_HALF_UP));
13 } catch (NamingException e) {
14 throw new EJBException(e);
15 } catch (CreateException e) {
16 throw new EJBException(e);
17 } catch (RemoteException e) {
18 throw new EJBException(e);
19 }
20 }
21
22 public void setSessionContext(SessionContext c) { this.ctx = c; }
23 public void ejbCreate() {}
24 public void ejbActivate() {}
25 public void ejbPassivate() {}
26 public void ejbRemove() {}
27}
META-INF/ejb-jar.xml (excerpt)XML
1<container-transaction>
2 <method>
3 <ejb-name>PolicyServiceBean</ejb-name>
4 <method-name>quote</method-name>
5 </method>
6 <trans-attribute>Required</trans-attribute>
7</container-transaction>

STRUTS 1.x / 2.x

Validation that is really policy.

The policy number pattern becomes a JSON Schema constraint. The $250,000 loss amount check is not formatting: it routes the claim to a special investigation referral page through the referral forward. PYD ENTERPRISES extracts it as a routing rule and ties it to the action mapping, the form bean and the JSP that renders the outcome. Struts 2 actions, interceptor stacks and -validation.xml files are handled the same way.

ClaimFnolForm.java (Struts 1.3)Java
1public class ClaimFnolForm extends ActionForm {
2 private String policyNumber;
3 private String lossDate;
4 private String lossAmount;
5
6 public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
7 ActionErrors errors = new ActionErrors();
8 if (policyNumber == null || !policyNumber.matches("[A-Z]{2}\\d{8}")) {
9 errors.add("policyNumber", new ActionMessage("error.policy.format"));
10 }
11 if (lossAmount != null && new BigDecimal(lossAmount).compareTo(new BigDecimal("250000")) > 0) {
12 errors.add("lossAmount", new ActionMessage("error.loss.referral"));
13 }
14 return errors;
15 }
16}
WEB-INF/struts-config.xml (excerpt)XML
<action path="/claim/fnol" type="com.carrier.claims.web.ClaimFnolAction"
name="claimFnolForm" scope="request" validate="true"
input="/claims/fnol.jsp">
<forward name="success" path="/claims/fnolConfirm.jsp"/>
<forward name="referral" path="/claims/siuReferral.jsp"/>
</action>

SPRING XML & AOP

Advice that changes answers is a rule.

Bean definitions, property injection, factory methods and profiles are resolved into the same graph as the Java classes. Pointcut expressions are matched against real method signatures, so PYD ENTERPRISES knows that MinimumPremiumInterceptor wraps every *Premium method in the rating package and applies a 250.00 floor. That floor would vanish in a rewrite that only read the service classes.

applicationContext-rating.xmlXML
1<bean id="minimumPremiumAdvice" class="com.carrier.rating.aop.MinimumPremiumInterceptor">
2 <property name="floor" value="250.00"/>
3</bean>
4<aop:config>
5 <aop:pointcut id="ratingOps"
6 expression="execution(java.math.BigDecimal com.carrier.rating..*Service.*Premium(..))"/>
7 <aop:advisor advice-ref="minimumPremiumAdvice" pointcut-ref="ratingOps"/>
8</aop:config>

SOAP, WSDL & XSD

Contracts carried forward with their constraints.

WSDL port types become OpenAPI 3.1 operations and XML Schema types become JSON Schema 2020-12. Facets such as pattern, enumeration, minOccurs and totalDigits are translated, and xsd:decimal is carried as a string with a decimal pattern so no client parses money as a binary float.

RatingService.wsdl (types)XML
<xsd:simpleType name="StateCode">
<xsd:restriction base="xsd:string">
<xsd:pattern value="[A-Z]{2}"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="QuoteRequest">
<xsd:sequence>
<xsd:element name="state" type="tns:StateCode"/>
<xsd:element name="classCode" type="xsd:string"/>
<xsd:element name="limit" type="xsd:decimal"/>
<xsd:element name="priorClaims" type="xsd:int" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
schemas/QuoteRequest.json (generated)JSON
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "QuoteRequest",
"type": "object",
"required": ["state", "classCode", "limit"],
"properties": {
"state": { "type": "string", "pattern": "^[A-Z]{2}$" },
"classCode": { "type": "string" },
"limit": { "type": "string", "pattern": "^-?\\d+(\\.\\d+)?$" },
"priorClaims": { "type": "integer", "format": "int32" }
},
"x-pyd-source": "RatingService.wsdl:41-58"
}

Microservice decomposition

See what each boundary costs before you draw it.

PYD ENTERPRISES groups components by the tables they write and the container transactions they share, then shows remote calls, shared writes and split transactions for each candidate. Move the slider across a 412k LOC policy administration system.

Services
5
Cross-boundary calls
23
Shared table writes
1
Transactions split
3

Rating

64k LOC
  • RatingServiceBeanSession
RATE_TABLE

Policy

107k LOC
  • PolicyQuoteActionStruts
  • PolicyIssueActionStruts
  • PolicyServiceBeanSession
  • PolicyEJBEntity
POLICYCOVERAGE

Claims

128k LOC
  • ClaimFnolActionStruts
  • ClaimAdjustActionStruts
  • ClaimServiceBeanSession
  • ClaimEJBEntity
CLAIMRESERVE

Billing

71k LOC
  • BillingInquiryActionStruts
  • BillingServiceBeanSession
  • InvoiceEJBEntity
INVOICEPAYMENT

Customer & documents

42k LOC
  • CustomerServiceBeanSession
  • DocumentServiceBeanSession
  • CustomerEJBEntity
CUSTOMERDOC_ARCHIVE

Recommended. Rating becomes a pure decision service (stateless, read-only RATE_TABLE). One shared write remains: PolicyServiceBean updates CUSTOMER.ADDRESS during issuance.

  • CUSTOMER written by PolicyServiceBean and CustomerServiceBean

EJB 2.x and 3.x

Two generations of the spec, one model.

ConcernEJB 2.x (descriptor-driven)EJB 3.x (annotation-driven)
Home and remote interfaces, JNDI lookupsResolved to the implementing bean@EJB injection and @Remote / @Local resolved
Transaction attributesejb-jar.xml container-transaction@TransactionAttribute, with XML overrides
Entity persistenceCMP 2.x fields, CMR relationships, EJB QLJPA entities, JPQL, orm.xml overrides
Securitymethod-permission and security-role-ref@RolesAllowed, @PermitAll
MessagingMessage-driven beans, destination in deployment descriptor@MessageDriven with activation config

FAQ

Java EE questions.

Does PYD ENTERPRISES need the application to run, or a working build?

No. Analysis is static. PYD ENTERPRISES parses source and deployment descriptors (ejb-jar.xml, struts-config.xml, Spring XML, web.xml, vendor descriptors) and resolves classes against the JARs you supply. A full build is helpful for resolving generated code but not required.

What happens with code generated by EJB tooling, such as stubs?

Generated stubs, skeletons and container classes are recognized and classified as plumbing. Calls through them are linked to the bean implementation, so the call graph shows business methods, not RMI machinery.

Can PYD ENTERPRISES find business rules hidden in Struts validation?

Yes. validate() methods and Struts Validator XML are parsed. Format checks become JSON Schema constraints; thresholds that route the request differently, such as a referral above a loss amount, become rules in the inventory.

How does the boundary recommender decide?

From the call graph, table read and write sets, and transaction scopes. It groups components that write the same tables or share container-managed transactions, then reports remote calls, shared writes and transactions that would need a saga for each candidate split. Architects choose; PYD ENTERPRISES shows the cost.

Does it handle Spring AOP that changes behavior?

Yes. Pointcut expressions are evaluated against the resolved class graph, so advice is attached to every method it actually intercepts. An interceptor that changes a return value, such as a minimum premium floor, is extracted as a rule on each affected operation.

Find the boundaries hiding in your EAR.

Run PYD ENTERPRISES on 100,000 lines of COBOL or Java in your private VPC and review OpenAPI specs and DMN tables tomorrow.