DEV Community

Cover image for Securing Legacy Java Monoliths with Runtime Zero-Trust: My Open-Source Framework LingFrame
LingFrame Β· 灡珑
LingFrame Β· 灡珑

Posted on • Edited on

Securing Legacy Java Monoliths with Runtime Zero-Trust: My Open-Source Framework LingFrame

The Problem No One Wants to Talk About πŸ¦–

Let's be honest.

You have a giant Java monolith. It's been running for years. It makes money. And it's held together by duct tape and prayers.

Every time you onboard a new team or integrate a vendor script, you think: "What if this code accidentally calls something it shouldn't? What if a junior dev's DTO leaks sensitive data? What if we need to A/B test this new module without a full restart?"

The usual advice is: "Just rewrite it as microservices!" Right. Let me just pause business for 18 months to refactor 500k LOC. πŸ™ƒ

This was my frustration. I believed there had to be a middle groundβ€”a way to introduce runtime governance into an existing JVM process, without tearing everything down.

So, I built LingFrame.


What is LingFrame? πŸ›‘οΈ

LingFrame is a JVM runtime governance framework for long-running Java applications.

Think of it as putting guardrails inside your process. It doesn't replace your architecture; it lets your existing monolith become governable.

Core Features (The "Why You Should Care" List):

Feature What it Means for You
Three-Tier ClassLoader Isolated plugins. No more "Jar Hell". Each business module gets its own sandbox.
Capability-Based Security Plugins must declare what they need (storage:sql, cache:local). Everything else is denied.
@LingService & @LingReference Expose and consume services across plugin boundaries with simple annotations.
Audit & Trace (@Auditable) Every sensitive operation is logged. Who did what, when, and to what resource.
Canary Deployments Run a new version of a plugin on a subset of traffic without restarting the JVM.

Show Me The Code (5-Minute Demo) ⏱️

Step 1: Add the Starter

In your Host Application's pom.xml:

<dependency>
    <groupId>com.lingframe</groupId>
    <artifactId>lingframe-spring-boot3-starter</artifactId>
    <version>0.1.0-Preview</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure LingFrame

In application.yaml:

lingframe:
  enabled: true
  dev-mode: true  # Logs warnings instead of blocking (for development)

  # Where your plugin JARs live
  plugin-home: plugins

  # For local development, point directly to source code
  plugin-roots:
    - ../my-user-plugin
    - ../my-order-plugin

  # Shared API (interfaces between host and plugins)
  preload-api-jars:
    - shared-api/order-api.jar

  # Enable the visual dashboard
  dashboard:
    enabled: true
Enter fullscreen mode Exit fullscreen mode

Step 3: Declare Plugin Capabilities (plugin.yml)

Each plugin must declare what it intends to access. This is the core of "Zero-Trust".

# In your plugin's resources/plugin.yml
id: user-plugin
version: 1.0.0
mainClass: com.example.user.UserPluginApplication

governance:
  capabilities:
    - capability: "storage:sql"
      accessType: "WRITE"       # We need to read/write to DB
    - capability: "cache:local"
      accessType: "WRITE"       # We need to use the local cache
    - capability: "ipc:order-plugin"
      accessType: "EXECUTE"     # We want to call the order service
Enter fullscreen mode Exit fullscreen mode

Step 4: Annotate Your Services

This is what your plugin code looks like:

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {

    private final JdbcTemplate jdbcTemplate;

    @LingService(id = "query_user", desc = "Query user by ID")
    @RequiresPermission(Capabilities.STORAGE_SQL) // Declare DB access
    @Cacheable(value = "users", key = "#userId")   // Also uses cache
    @Auditable(action = "QUERY_USER", resource = "user") // Audit this
    @Override
    public Optional<UserDTO> queryUser(String userId) {
        log.info("Cache miss for user {}", userId);
        String sql = "SELECT * FROM t_user WHERE id = ?";
        // ... jdbcTemplate query ...
        return Optional.ofNullable(user);
    }

    @LingService(id = "create_user", desc = "Create a new user")
    @RequiresPermission(Capabilities.STORAGE_SQL)
    @Auditable(action = "CREATE_USER", resource = "user")
    @Override
    public UserDTO createUser(String name, String email) {
        // ... INSERT SQL ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Call Across Plugins with @LingReference

Need to call user-plugin from order-plugin? Don't use @Autowired. Use @LingReference:

@Service
public class OrderServiceImpl implements OrderService {

    @LingReference // LingFrame routes this to the 'user-plugin'
    private UserQueryService userQueryService;

    @LingService(id = "get_order", desc = "Get order by ID")
    public OrderDTO getOrderById(Long orderId) {
        // ... get order from DB ...

        // Cross-plugin call (IPC)
        userQueryService.findById(userId).ifPresent(
            user -> order.setUserName(user.getName())
        );
        return order;
    }
}
Enter fullscreen mode Exit fullscreen mode

See The Governance in Action πŸ”₯

Scenario: Unauthorized Write Attempt

Imagine a plugin declares only READ access to storage:sql, but then tries to run an INSERT.

curl -X POST "http://localhost:8888/user-plugin/user/createUser?name=Hacker&email=h@ck.er"
Enter fullscreen mode Exit fullscreen mode

Result: LingFrame blocks it.

c.l.core.exception.PermissionDeniedException: 
Plugin [user-plugin] requires [storage:sql] with [WRITE] access, 
but only allowed: [READ]
Enter fullscreen mode Exit fullscreen mode

The plugin fails gracefully. The host app stays alive. The event is audited.

Visual Dashboard

LingFrame includes a built-in dashboard. You can see:

  • Real-time plugin status (RUNNING, STOPPED, CANARY)
  • Traffic split for Canary deployments
  • Live audit logs

LingFrame Dashboard


Why Not Just Use [OSGi / Spring Cloud / etc.]?

Framework Trade-off
OSGi Very powerful, but has a brutal learning curve and changes your entire dev lifecycle.
Spring Cloud / K8s Solves isolation by splitting processes. This means network hops, serialization, and infrastructure complexity.
LingFrame In-process isolation. Microservice-like boundaries, but with local method call performance.

LingFrame is for teams that can't do a full microservices split right now, but need better governance today.


Current Status & Roadmap πŸ—ΊοΈ

LingFrame is in v0.1.x Preview. The direction is set, but we're still validating the core loop.

What's Done:

  • [x] Three-tier ClassLoader isolation
  • [x] Capability-based permission model
  • [x] Spring Boot 3 integration
  • [x] Built-in visual dashboard
  • [x] Canary deployment support

What's Next:

  • [ ] JDK 8 / Spring Boot 2.x compatibility layer
  • [ ] Resilience governance (Circuit breaker, Rate limiting, Retry)
  • [ ] Observability (Metrics, Distributed tracing export)

Try It Out πŸš€

If you're wrestling with an aging monolith and dreaming of better governance, give LingFrame a look.

πŸ‘‰ GitHub: LingFrame/LingFrame ⭐

How to Start:

git clone https://github.com/LingFrame/LingFrame.git
cd LingFrame
mvn clean install -DskipTests
cd lingframe-examples/lingframe-example-host-app
mvn spring-boot:run
Enter fullscreen mode Exit fullscreen mode

Then visit http://localhost:8888/dashboard.html to see the UI.


Star the repo if this resonates with you! ⭐

Drop a comment: What's the messiest legacy system you've had to deal with? How did you handle untrusted code?

(This is Part 1 of my "JVM Runtime Governance" series. Next up: A deep dive into our three-tier ClassLoader architecture!)


Series: #lingframe-series
#java #opensource #security #zerotrust #springboot #architecture

Top comments (1)

Collapse
 
linglongger profile image
LingFrame Β· 灡珑

βœ… JDK 8 / Spring Boot 2.7.18 is supported.