Opinionated rules for building performant, secure, and maintainable hybrid applications that share business logic between Java back-ends and JavaScript/TypeScript front-ends/mobile clients.
Your team maintains two codebases with duplicated business logic, inconsistent validation rules, and API contracts that drift out of sync. Every feature requires implementing the same rules twice – once in your Spring Boot backend and again in your React/React Native frontend. Security vulnerabilities slip through because validation differs between layers. Sound familiar?
Most development teams treat Java backends and JavaScript frontends as completely separate worlds. This architectural split creates expensive problems:
Duplicated Business Logic: Your order validation exists in both your Spring service AND your React form components. When business rules change, you update two places – or forget one and ship bugs.
API Contract Drift: Your OpenAPI spec says the endpoint returns a customerId field, but your frontend expects customer_id. These mismatches cause runtime failures that unit tests miss.
Inconsistent Security: Your Java backend validates email formats with regex, but your JavaScript frontend uses a different validation library with different rules. Attackers exploit these gaps.
Context Switching Overhead: Developers constantly switch between Java and JavaScript paradigms, losing 15-20 minutes of deep focus with each transition.
Testing Complexity: Integration tests become nightmares because you're testing the integration between two completely different codebases with different assumptions.
These Cursor Rules establish a hybrid development approach where Java and JavaScript share business logic, validation rules, and type contracts. Instead of building two applications, you build one cohesive system with platform-specific presentation layers.
Here's what changes:
// Java - Domain Service
@Service
public class OrderValidationUseCase {
public ValidationResult validateOrder(OrderRequest order) {
return ValidationResult.builder()
.addError(order.email(), EmailValidator::isValid, "Invalid email format")
.addError(order.amount(), amount -> amount.compareTo(BigDecimal.ZERO) > 0, "Amount must be positive")
.build();
}
}
// TypeScript - Generated from Java Contract
export interface OrderRequest {
email: string;
amount: number;
customerId: string; // Always matches Java field names
}
// Client-side validation using shared rules
export const validateOrder = (order: OrderRequest): ValidationResult => {
// Generated from Java validation logic
return validateOrderContract(order);
};
Your API contracts become the single source of truth. When you change a Java DTO, your TypeScript interfaces update automatically:
// Java DTO
public record CustomerProfile(
@NotNull String customerId,
@Email String email,
@Size(min = 2, max = 50) String firstName,
Optional<Address> billingAddress
) {}
// Auto-generated TypeScript (via openapi-generator)
export interface CustomerProfile {
customerId: string;
email: string;
firstName: string;
billingAddress?: Address;
}
Time cost: 3-4 days per feature with validation logic
Time cost: 1 day per feature, zero contract drift
Before: Write regex validation in Java, copy to JavaScript, hope they stay in sync After: Define validation once in Java bean validation, generate TypeScript validators automatically
// Single definition
public record PaymentMethod(
@Pattern(regexp = "^[0-9]{13,19}$", message = "Invalid card number")
String cardNumber,
@CreditCardType Set<CardType> acceptedTypes
) {}
Before: Update Java DTO, manually update TypeScript interfaces, fix compilation errors across codebase After: Update Java DTO, run generation script, TypeScript compiler catches all breaking changes
Before: Implement JWT validation in Spring Security, separately implement token parsing in JavaScript After: Share JWT validation logic between platforms using GraalVM polyglot runtime
npx create-nx-workspace@latest my-hybrid-app --preset=empty
cd my-hybrid-app
# Add Java Spring Boot app
nx g @nrwl/spring-boot:app api
# Add TypeScript web app
nx g @nrwl/react:app web
# Add React Native app
nx g @nrwl/react-native:app mobile
Add OpenAPI generation to your Maven build:
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/openapi.yaml</inputSpec>
<generatorName>typescript-fetch</generatorName>
<output>${project.basedir}/../libs/api-client</output>
</configuration>
</execution>
</executions>
</plugin>
Use Bean Validation in Java and generate equivalent TypeScript validators:
@RestController
@Validated
public class OrderController {
@PostMapping("/orders")
public ResponseEntity<OrderResponse> createOrder(
@Valid @RequestBody OrderRequest request
) {
// Validation happens automatically
return ResponseEntity.ok(orderService.create(request));
}
}
Configure your build to generate contracts on Java changes:
// package.json
{
"scripts": {
"contracts:generate": "mvn compile -pl api && nx run api-client:build",
"dev": "concurrently \"nx serve api\" \"nx serve web\" \"npm run contracts:generate --watch\""
}
}
Teams using this hybrid approach report:
The bottom line: Stop building two applications. Build one cohesive system with platform-specific presentation layers. Your team will ship faster, maintain less code, and sleep better knowing your business logic stays consistent across every platform.
Start with these Cursor Rules and transform your next feature from a multi-codebase nightmare into a single-source-of-truth success story.
You are an expert in modern Java (17+), JavaScript/TypeScript (ES2022), GraalVM polyglot tooling, Spring Boot, Vaadin, React Native, JHipster, Quarkus, Capacitor/Ionic, and monorepo tooling (Nx, Turborepo).
# Key Principles
- Single Source of Truth: keep business rules in shared Kotlin/Java modules or TypeScript libraries re-used across targets.
- Progressive Enhancement: ship a working PWA first, sprinkle native modules only when UX demands.
- Prefer functional, declarative code over imperative, state-heavy patterns.
- Fail fast → validate inputs first, return early, log clearly.
- Dependency inversion everywhere: wire objects through Spring/DI containers, avoid new in production code.
- Consistency over cleverness: follow naming/style guides strictly.
- Automate everything (CI/CD, lint, test, security scan) before code reaches main.
# Java Rules (Back-end & Shared Logic)
- Target Java 17+ and enable `--enable-preview` only in experimental branches.
- Package layout: `com.<company>.<domain>.<layer>` (e.g., `com.acme.orders.service`).
- Use records for immutable DTOs; treat them as boundaries between layers and across HTTP/GraphQL.
- Service classes must be stateless, interfaces suffixed with `UseCase` (e.g., `PlaceOrderUseCase`).
- Mandatory annotations: `@NonNull` on parameters, `@Validated` on Spring controllers.
- Avoid checked exceptions in service layer; convert to domain-specific runtime exceptions.
- Use `sealed` hierarchies for error enums and command/result types.
- Prefer Spring Data projections instead of entities in REST/GraphQL payloads.
# JavaScript / TypeScript Rules (Web, Mobile, Node APIs)
- All JS code is TypeScript (`.ts` / `.tsx`), strict mode on, `noImplicitAny` true.
- Directory casing: kebab-case for folders, PascalCase for React components.
- Functions first-class; avoid classes unless extending framework base (e.g., `Error`).
- Use ES Modules everywhere; no CommonJS.
- Top-level await ONLY inside build-time or Node scripts, never inside React code.
- Interfaces over type aliases for public contracts; use `type` for unions/intersections.
- React / RN components: prefer arrow function components with explicit return type.
- Keep hook names descriptive (e.g., `useIsAuthenticated`).
# Error Handling and Validation
- Java:
- Controller advice -> single `@RestControllerAdvice` translates domain errors to Problem+JSON.
- Use `ConstraintValidator` for custom validation; never validate in controller body manually.
- JavaScript/TypeScript:
- Always return `Result<T, E>` (fp-ts/Zod) or use `async/await` wrapped in `try/catch` with typed error object.
- Client → Back-end errors: inspect HTTP status, map to domain-level error codes before UI.
- Logging: use structured logs (JSON) with correlation/id propagated via `X-Request-Id` header.
# Framework-Specific Rules
## Spring Boot
- Enable Spring Boot DevTools locally; disable in all builds with `mvn -Pprod`.
- Layered architecture: `web → application → domain → infrastructure` packages.
- All endpoints documented via springdoc-openapi; CI fails if spec diff is detected.
- Security: OAuth2 Resource Server + JWT. No session state.
- Non-blocking endpoints (WebFlux) for high-latency operations.
## React Native (Expo or Bare)
- Only functional components + React Hooks.
- Navigation via React Navigation; screens registered in dedicated `navigation/*.tsx`.
- Never block JS thread (no heavy JSON stringify/parse > 16 ms). Offload to native module or Web Worker.
- Use `@react-native-community/netinfo` for online/offline detection and queue sync.
- Theme via `styled-components/native` with `ThemeProvider`.
## Vaadin (for desktop-like web)
- All views extend `VerticalLayout` with constructor wiring; no field injection in UI layer.
- Use `@PWA` with offline resources list generated by build.
## JHipster
- Generate project once; do NOT re-generate after manual edits. Use blueprints.
- Replace default JWT with OAuth2 if multiple SPAs needed.
# Testing
- Java: JUnit 5 + Testcontainers for integration DB. WireMock for external HTTP.
- JavaScript: Jest + React Testing Library; Detox/E2E for RN.
- Coverage gates: 85% lines, 70% branches minimum.
- TDD rule: write failing test before bug fix or new feature.
# Performance
- Instrument with Java Flight Recorder & React Native Flipper.
- Back-end: cache expensive queries via Caffeine or Redis TTL ≤ 5 min.
- Front-end: code-split via dynamic `import()` and React.lazy.
- Set `priority` attribute for above-the-fold images (Next.js) or `FastImage` in RN.
# Security
- Enable CSP headers with nonce in Spring Security configuration.
- Use OWASP dependency-check in Maven/Gradle & `npm audit --production` in CI.
- Store secrets in Vault or GitHub OIDC; never commit `.env` files.
# CI/CD & Tooling
- Monorepo root uses Nx; define `apps/` (`api`, `web`, `mobile`), `libs/` for shared.
- `_pre-push` git hook runs lint (`eslint --max-warnings=0`), type check, unit tests.
- Release pipeline: build → test → static scan → container scan → deploy to staging → e2e → prod.
- Use semantic-release; version bump and changelog automatic.
# Accessibility & Localization
- React Native: use `accessibilityLabel`, `accessible`, `importantForAccessibility`.
- Web (Vaadin/React): all interactive elements reachable via keyboard; lighthouse a11y score ≥ 90.
- I18n files extracted from code via `i18next-parser`, translation completeness checked in CI.
# Documentation
- Public API contracts in `openapi.yaml`, auto-published to developer portal.
- ADRs (#000-title.md) in `/docs/adr/`; new architecture decisions require PR with ADR.
# Directory Skeleton (Monorepo)
```
my-hybrid-app/
├── apps/
│ ├── api (Spring Boot)
│ ├── web (Next.js)
│ └── mobile (React Native)
├── libs/
│ ├── auth (shared TS + Java JVM lib via GraalVM polyglot)
│ ├── ui-components
│ └── domain
├── infra/
│ └── k8s/
├── tools/ (scripts, schematics)
└── docs/
```
# Common Pitfalls & Guards
- 🚫 Tight coupling between mobile UI and REST shape → always mediate via typed client SDK generated from OpenAPI.
- 🚫 Synchronous blocking inside event loop (Node) or React Native JS thread.
- 🚫 Cross-platform native feature divergence (e.g., file system) → add abstraction layer then per-platform implementation.
Follow this ruleset to keep hybrid applications fast, safe, and maintainable while maximizing code reuse between Java and JavaScript worlds.