Hashtag Jakarta EE #316

Hashtag Jakarta EE #316

Welcome to issue number three hundred and sixteen of Hashtag Jakarta EE!

The conference speaking season is starting up again. My first talk of the year will be at Valencia JUG where I will present The Past, Present, and Future of Enterprise Java. After that, I will be attending FOSDEM in Brussels. These two events conclude my January.

In February, I will be helping out at Jfokus as a stage host before going to Johannesburg to speak at JakartaOne by Jozi-JUG. While I am in South Africa, I am pleased that Bruno Souza will visit Malmö and present at Javaforum Malmö. Then I will speak at DeveloperWeek in San Jose and ConFoo in Montreal.

Use this link for a free OPEN Pass to DevelopeWeek 2026 ($195 value).

The Jakarta EE Platform project is working toward Milestone 2 of Jakarta EE 12. The milestone date is January 27, 2026. Check out the progress here and be sure to check out the minutes from the weekly platform call if you aren’t able to attend.

The videos from JakartaOne Livestream 2025 are published. Check them out at https://jakartaone.jakarta.ee/2025

Ivar Grimstad


Public vs Private APIs in ASP.NET Core — Branching the Middleware Pipeline (Production‑Minded, with a Smile)

Public vs Private APIs in ASP.NET Core — Branching the Middleware Pipeline (Production‑Minded, with a Smile)<br>

Public vs Private APIs in ASP.NET Core — Branching the Middleware Pipeline (Production‑Minded, with a Smile)

Imagine you run two API “surfaces” in the same ASP.NET Core app:

  • Public: /_api/... (external clients, OData, public contracts)
  • Private: /api/... (internal services, admin portals, trusted callers)

And you have a set of security middlewares like:

  • DenySecretsInUrlMiddleware
  • BlockSensitiveQueryStringMiddleware
  • TokenAgeGuardMiddleware
  • SecuritySignalsMiddleware
  • AuthProblemDetailsMiddleware

But you only want them to execute for one surface (e.g., public only, or private only) — without polluting each middleware with “public/private” logic.

The most professional way in ASP.NET Core is to branch the request pipeline.

It’s basically Chain of Responsibility, but split into branches based on a predicate.

Table of Contents

  • Release Overview
  • The Problem in One Sentence
  • Why Pipeline Branching Is the Right Pattern
  • Option A — UseWhen by Prefix (Recommended)

    • A1) Run middlewares only for Public /_api
    • A2) Run middlewares only for Private /api
    • A3) Keep Startup Clean with Extensions
  • Option B — Endpoint Metadata Profiles (Enterprise Flex)
  • Option C — Route Groups (If You Move to Minimal APIs)
  • DI: “Don’t Register” vs “Don’t Execute”
  • Production Notes (OData + Controllers + Ordering)
  • Common Pitfalls
  • Final Recommendation

Release Overview

You’re building an API host that:

  • Exposes both public and private endpoints:
    • /_api/report/v1/... (public)
    • /api/report/v1/... (private)
  • Uses Controllers + OData
  • Has a security middleware stack that should apply only to specific surfaces.

This guide gives you multiple approaches, from fastest-to-ship to maximum flexibility.

The Problem in One Sentence

You want the same application to have different middleware chains depending on whether the request is for /_api/... or /api/....

Why Pipeline Branching Is the Right Pattern

Because it gives you:

  • Separation of concerns: middlewares stay “pure” (no route logic inside them).
  • Single source of truth: route-based security policy lives in one place (Startup).
  • Performance: requests that don’t match the branch don’t even instantiate/execute those middlewares.
  • Evolvability: easy to extend from prefixes to endpoint metadata later.

Option A — UseWhen by Prefix (Recommended)

This is the cleanest solution when your contract already has clear prefixes.

A1) Run middlewares only for Public /_api

Use this when public requests should run your security middlewares, but private ones should not.

Place it after UseRouting() and before endpoint mapping (MapControllers() / UseEndpoints()):

app.UseRouting();

// ✅ Branch: only /_api
app.UseWhen(
    ctx => ctx.Request.Path.StartsWithSegments("/_api", StringComparison.OrdinalIgnoreCase),
    branch =>
    {
        branch.UseMiddleware<DenySecretsInUrlMiddleware>();
        branch.UseMiddleware<BlockSensitiveQueryStringMiddleware>();
        branch.UseMiddleware<TokenAgeGuardMiddleware>();
        branch.UseMiddleware<SecuritySignalsMiddleware>();
        branch.UseMiddleware<AuthProblemDetailsMiddleware>();
    });

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

A2) Run middlewares only for Private /api

Same idea, different predicate:

app.UseWhen(
    ctx => ctx.Request.Path.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase),
    branch =>
    {
        branch.UseMiddleware<DenySecretsInUrlMiddleware>();
        branch.UseMiddleware<BlockSensitiveQueryStringMiddleware>();
        branch.UseMiddleware<TokenAgeGuardMiddleware>();
        branch.UseMiddleware<SecuritySignalsMiddleware>();
        branch.UseMiddleware<AuthProblemDetailsMiddleware>();
    });

A3) Keep Startup Clean with Extensions

Large Startup files become unreadable. Encapsulate the branching logic.

public static class ApiSecurityPipelineExtensions
{
    public static IApplicationBuilder UsePublicApiSecurity(this IApplicationBuilder app)
    {
        return app.UseWhen(
            ctx => ctx.Request.Path.StartsWithSegments("/_api", StringComparison.OrdinalIgnoreCase),
            branch =>
            {
                branch.UseMiddleware<DenySecretsInUrlMiddleware>();
                branch.UseMiddleware<BlockSensitiveQueryStringMiddleware>();
                branch.UseMiddleware<TokenAgeGuardMiddleware>();
                branch.UseMiddleware<SecuritySignalsMiddleware>();
                branch.UseMiddleware<AuthProblemDetailsMiddleware>();
            });
    }

    public static IApplicationBuilder UsePrivateApiSecurity(this IApplicationBuilder app)
    {
        return app.UseWhen(
            ctx => ctx.Request.Path.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase),
            branch =>
            {
                branch.UseMiddleware<DenySecretsInUrlMiddleware>();
                branch.UseMiddleware<BlockSensitiveQueryStringMiddleware>();
                branch.UseMiddleware<TokenAgeGuardMiddleware>();
                branch.UseMiddleware<SecuritySignalsMiddleware>();
                branch.UseMiddleware<AuthProblemDetailsMiddleware>();
            });
    }
}

Use it in your Configure():

app.UseRouting();

app.UsePublicApiSecurity();  // or app.UsePrivateApiSecurity();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

Option B — Endpoint Metadata Profiles (Enterprise Flex)

Prefix-based routing is great… until you want exceptions like:

  • some /_api endpoints should skip middlewares,
  • or some /api endpoints should opt-in.

Solution: endpoint metadata.

1) Define the profile attribute

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public sealed class SecurityMiddlewareProfileAttribute : Attribute
{
    public SecurityMiddlewareProfileAttribute(string profile) => Profile = profile;
    public string Profile { get; }
}

public static class SecurityProfiles
{
    public const string Public = "public";
    public const string Private = "private";
    public const string None = "none";
}

2) Mark your controllers/actions

[SecurityMiddlewareProfile(SecurityProfiles.Public)]
[Route("_api/report/v1/[controller]")]
public class ReportController : ControllerBase
{
    // ...
}

3) Branch using endpoint metadata

Important: endpoint metadata is available after routing.

app.UseRouting();

app.UseWhen(ctx =>
{
    var ep = ctx.GetEndpoint();
    var profile = ep?.Metadata.GetMetadata<SecurityMiddlewareProfileAttribute>()?.Profile;

    return string.Equals(profile, SecurityProfiles.Public, StringComparison.OrdinalIgnoreCase);
},
branch =>
{
    branch.UseMiddleware<DenySecretsInUrlMiddleware>();
    branch.UseMiddleware<BlockSensitiveQueryStringMiddleware>();
    branch.UseMiddleware<TokenAgeGuardMiddleware>();
    branch.UseMiddleware<SecuritySignalsMiddleware>();
    branch.UseMiddleware<AuthProblemDetailsMiddleware>();
});

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

✅ Result: you control middleware behavior per endpoint, declaratively.

Option C — Route Groups (If You Move to Minimal APIs)

If you ever migrate to Minimal APIs, route groups make this chef’s kiss:

var publicApi = app.MapGroup("/_api");
publicApi.UseMiddleware<DenySecretsInUrlMiddleware>();
publicApi.UseMiddleware<BlockSensitiveQueryStringMiddleware>();

var privateApi = app.MapGroup("/api");
// privateApi.UseMiddleware<...>();

Controllers don’t support route groups in the same first-class way, so for now, UseWhen remains the safest.

DI: “Don’t Register” vs “Don’t Execute”

You said: “when private, I don’t want to execute them or even register them in dependencies.”

In ASP.NET Core, you don’t need to unregister them to prevent execution.

When you place them inside UseWhen(...):

  • they are not executed for non-matching routes
  • they are typically not resolved for non-matching routes
  • your app stays simple and consistent (especially in multi-env)

“Not executing” is the real objective. “Not registering” is usually unnecessary complexity.

If you still want to avoid registration for some deployments, treat that as an environment configuration concern (e.g., only register certain middlewares in prod/public deployments).

Production Notes (OData + Controllers + Ordering)

Ordering rule of thumb

  • UseRouting() must run before any branch that checks route metadata.
  • Branching by path prefix can happen right after UseRouting() (or even before), but it’s clean after routing.
  • Branching by endpoint metadata must be after UseRouting().

Typical robust order

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

// Branch here
app.UsePublicApiSecurity();

app.UseAuthentication();
app.UseAuthorization();

app.UseRateLimiter();

app.MapControllers();
app.MapHub<CallsHub>("/callshub");

Common Pitfalls

  • Putting UseWhen too late (after endpoint mapping): it won’t affect requests.
  • Inspecting endpoint metadata before UseRouting: GetEndpoint() will be null.
  • Using both prefix checks and metadata without a clear rule: you’ll debug “why is middleware running twice?”
  • Over-coupling middlewares to “public/private” logic: keep them composable and pure.

Final Recommendation

  • Start with Option A (UseWhen by prefix): it matches your current API surfaces (/_api vs /api) and ships fast.
  • When you need fine control per endpoint, evolve to Option B (metadata profiles).
  • Keep middlewares pure; keep “when to run them” in the pipeline configuration.

If you paste how your Controllers are routed (including OData routes), I can produce the exact UseWhen block and the safest ordering for your app (so it won’t clash with OData routing or UseEndpoints()).

Happy shipping. 🚀

Music Monday: Taking a ‘Break’

This is going to be the final Music Monday for a little while! We’re taking a short hiatus to do some retooling behind the scenes. It’s been an absolute blast seeing the Spotify playlist grow, and I want to make sure the next iteration is even better for the community.

In the spirit of taking a “break,” today’s theme is Breakbeats.

Born in the 80s streets of New York, breakbeat was the heartbeat of early Hip Hop, directly giving birth to breakdancing and serving as a foundational pillar of DJ culture as it evolved throughout the 90s and into the 2000s as the DNA for everything from Jungle and DnB to Trap and Dubstep. But don’t feel boxed in; if it’s got a rhythm that makes you want to move, share it!

Drop a YouTube, Bandcamp, Spotify, or SoundCloud link using the {% embed %} tag in the comments.

The “Designer Flow” for AI: Why I Built a Bridge to Google Stitch

We are living in the golden age of AI coding. We have Cursor, Windsurf, and Claude—tools that can write entire apps in seconds. But for UI design, we still possess a strange blind spot.

We usually have two choices:

  1. Describe a UI in text and hope the LLM hallucinates something pretty (it rarely does).
  2. Use a specialized tool like Google Stitch, get an amazing result, and then… manually copy-paste the code?

It felt like buying a Ferrari and pushing it down the driveway.

The “Context” Problem

I love Google Stitch. It generates stunning, production-ready screens. But it lives on an island. If I generate a “Home Screen,” my AI agent doesn’t know about it. When I ask for a “Profile Screen” next, the AI starts from scratch. Different fonts, different colors, different vibes.

I wanted my AI agent to see what I see.

So, I Built a Bridge.

I created stitch-mcp, an open-source connector that links Google Stitch directly to your AI workflow. It uses the Model Context Protocol (MCP) to give your agent safe, direct access to your design projects.

But I didn’t just want it to fetch files. I wanted it to understand Design.

The “Design DNA” Feature

The coolest part? I built a tool called extract_design_context.

Instead of dumping 5,000 lines of HTML into your chat window, this tool scans your screen and extracts exactly what matters:

  • The Colors: Your exact Tailwind palette.
  • The Typography: The fonts and weights you’re using.
  • The Structure: How your headers, navbars, and buttons are built.

It hands this “Design DNA” to your AI agent so that the next screen it generates looks exactly like it belongs in the same app.

How to Try It

It’s completely open-source. You can set it up in about 2 minutes.

https://github.com/Kargatharaakash/stitch-mcp

I built this because I needed it, but I think any developer who cares about UI consistency will love it.

If you give it a spin, I’d really appreciate a star ⭐️ on GitHub. It helps more people find it.

Happy designing. 🚀

TeamCity 2025.11.2 Is Now Available

The second bug-fix update for TeamCity On-Premises 2025.11 is ready to be installed! In this update, we have addressed a number of issues, such as:

  • The “buildagent” user in TeamCity Linux agent images changed its UID from 1000 to 1001;
  • Dark theme inconsistencies in the Custom Run dialog;
  • Builds with artifact dependencies may get stuck in the “Preparing to start” phase;
  • The Agent Open Terminal button may disappear from the agent details page.

All TeamCity bug-fix updates include performance and security improvements, so we recommend that you never skip these minor updates. See TeamCity 2025.11.2 Release Notes for the complete list of resolved issues.

Why update?

Staying up to date with minor releases ensures your TeamCity instance benefits from the following:

  • Performance improvements.
  • Better compatibility with integrations.
  • Faster, more stable builds.
  • Enhanced security for your workflows.

Compatibility

TeamCity 2025.11.2 shares the same data format as all 2025.11.x releases. You can upgrade or downgrade within this series without the need for backup and restoration.

How to upgrade

  1. Use the automatic update feature in your current TeamCity version.
  2. Download the latest version directly from the JetBrains website.
  3. Pull the updated TeamCity Docker image.

Need help?

Thank you for reporting issues and providing feedback! If you have questions or run into any problems, please let us know via the TeamCity Forum or Issue Tracker.

Happy building!

SSR vs SPA | Qual usar?

O objetivo deste artigo é guiá-los na escolha da melhor tecnologia para sua aplicação.

Quando iniciei minha carreira, comecei com JSF (JavaServer Faces), um MPA (Multi-Page Application) no qual o servidor entregava o HTML completamente renderizado para o navegador apenas exibir. Cada interação do usuário resultava em uma nova requisição e, consequentemente, em um novo carregamento de página.

Pouco tempo depois, o Ajax explodiu em popularidade — junto com o jQuery — e passou a permitir a atualização de apenas partes da página. Isso trouxe mais dinamismo às aplicações e reduziu significativamente a quantidade de reloads completos, melhorando a experiência do usuário sem abandonar o modelo server-side.

Em seguida, houve uma ruptura. Os dispositivos pessoais se tornaram mais potentes, os navegadores evoluíram e isso abriu espaço para utilizar o cliente como principal responsável pela renderização da interface. Nesse contexto, o modelo SPA (Single-Page Application) ganhou popularidade e passou a dominar o desenvolvimento frontend.

O SPA consolidou o backend como um provedor de APIs, separando de forma mais clara frontend e backend. Essa mudança ajudou a reduzir custos de infraestrutura e aumentou a flexibilidade arquitetural, mas também trouxe novos problemas: o primeiro carregamento das aplicações ficou mais lento e o SEO das páginas públicas piorou significativamente.

É nesse cenário que o SSR surge como uma tentativa de corrigir os problemas das SPAs sem simplesmente retornar ao modelo do passado. A página volta a ser renderizada no servidor, garantindo um carregamento inicial mais rápido e melhor indexação, mas após a entrega inicial ela passa a se comportar como uma SPA no navegador.

O que mudou ao longo do tempo não foi apenas a tecnologia, mas a forma como equilibramos responsabilidades entre servidor, cliente, performance e experiência do usuário.

Qual devo usar?
Antes de decidir entre SPA ou SSR, é fundamental responder a algumas perguntas básicas sobre o projeto:

  1. Para quem é esta aplicação?
    É um sistema interno, um painel administrativo, uma ferramenta autenticada ou uma aplicação pública voltada para usuários finais?

  2. Qual é o meu orçamento?
    Tenho recursos para manter uma infraestrutura mais complexa, com renderização no servidor, cache e possíveis custos adicionais de escalabilidade?

  3. Preciso que a primeira renderização seja rápida?
    O tempo até o primeiro conteúdo visível é crítico para a experiência do usuário?

  4. SEO é importante para a página?
    O conteúdo precisa ser indexado por mecanismos de busca ou compartilhado em redes sociais com preview adequado?

  5. Qual é o tamanho da aplicação?
    Mesmo que a primeira renderização precise ser rápida, nem sempre o tamanho e a complexidade da aplicação justificam o uso de SSR.

Para quem é o SPA
Use SPA quando a aplicação se comporta mais como um software do que como um site. Nesses cenários, o tempo da primeira renderização e o SEO não são fatores críticos.

Exemplos:
Ferramentas internas ou aplicações que exigem autenticação para acesso. Nesse tipo de sistema, as funcionalidades e informações não estão disponíveis ao público em geral, mas restritas a usuários específicos, como equipes internas, parceiros ou clientes autenticados.

Para quem é o SSR
Use SSR quando a aplicação se comporta mais como um site do que como um software. Nesses cenários, o tempo da primeira renderização e o SEO são fatores críticos para o sucesso do projeto.

Exemplos:
Sites institucionais, blogs, portais de conteúdo, e-commerces e landing pages. Em aplicações desse tipo, o conteúdo precisa estar disponível rapidamente, ser facilmente indexado por mecanismos de busca e gerar previews corretos ao ser compartilhado em redes sociais.

Conclusão
Durante minha carreira vi pequenas aplicações serem criadas com SSR antes validar se o SPA poderia atender. Vi aplicações pequenas com muito acesso necessitando de 16 servidores para atender a quantidade de requisições. Porém somente parte do conteúdo era púplico e a grande maioria era autenticado. Neste caso você poderia ter duas aplicações e reduzir a quantidade de servidores.

No fim, a melhor tecnologia é aquela que atende aos requisitos do negócio, equilibrando corretamente custo e benefícios.

Jekyll 블로그에 Mermaid 다이어그램 추가하기

기술 블로그를 운영하다 보면 아키텍처, 플로우차트, 시퀀스 다이어그램 등을 그려야 할 때가 많습니다. 이미지 파일을 별도로 만들어 첨부하는 방식은 번거롭고 수정도 어렵습니다. Mermaid.js를 사용하면 마크다운 코드 블록 안에서 텍스트로 다이어그램을 정의하고, 이를 자동으로 렌더링할 수 있습니다.

Mermaid란?

Mermaid는 텍스트 기반 다이어그램 생성 도구입니다. 마크다운과 유사한 문법으로 다양한 다이어그램을 만들 수 있습니다.

지원하는 다이어그램 종류:

  • Flowchart (플로우차트)
  • Sequence Diagram (시퀀스 다이어그램)
  • Class Diagram (클래스 다이어그램)
  • State Diagram (상태 다이어그램)
  • Entity Relationship Diagram (ERD)
  • Gantt Chart (간트 차트)
  • Pie Chart (파이 차트)
  • 그 외 다수

Jekyll에 Mermaid 설정하기

1. head.html에 스크립트 추가

_includes/head.html 파일에 다음 코드를 추가합니다:

<!-- Mermaid Diagram Support -->
<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
  mermaid.initialize({ startOnLoad: false });

  // Convert Jekyll code blocks to mermaid diagrams
  document.addEventListener('DOMContentLoaded', function() {
    document.querySelectorAll('pre code.language-mermaid, div.language-mermaid pre code').forEach(function(codeBlock) {
      const container = codeBlock.closest('div.language-mermaid') || codeBlock.parentElement;
      const pre = document.createElement('pre');
      pre.className = 'mermaid';
      pre.textContent = codeBlock.textContent;
      container.replaceWith(pre);
    });
    mermaid.run();
  });
</script>

이 스크립트는:

  1. Mermaid 라이브러리를 CDN에서 로드합니다
  2. Jekyll이 생성하는 코드 블록(language-mermaid)을 찾아 Mermaid가 인식할 수 있는 형태로 변환합니다
  3. 페이지 로드 완료 후 다이어그램을 렌더링합니다

사용 방법

마크다운 파일에서 mermaid 코드 블록을 사용하면 됩니다:

```

mermaid
graph TD
    A[시작] --> B{조건 확인}
    B -->|Yes| C[처리]
    B -->|No| D[종료]
    C --> D


```
```

`

**결과:**

```mermaid
graph TD
    A[시작] --> B{조건 확인}
    B -->|Yes| C[처리]
    B -->|No| D[종료]
    C --> D
```

---

## 다이어그램 예제

### Flowchart (플로우차트)

방향 옵션: `TB` (위→아래), `BT` (아래→위), `LR` (왼쪽→오른쪽), `RL` (오른쪽→왼쪽)

```mermaid
graph LR
    A[사용자 요청] --> B[API Gateway]
    B --> C[인증 서버]
    C --> D{인증 성공?}
    D -->|Yes| E[서비스 처리]
    D -->|No| F[401 에러]
    E --> G[응답 반환]
```

**노드 모양:**
- `[텍스트]` - 사각형
- `(텍스트)` - 둥근 모서리
- `{텍스트}` - 마름모 (조건)
- `((텍스트))` - 원
- `[[텍스트]]` - 서브루틴

---

### Sequence Diagram (시퀀스 다이어그램)

```mermaid
sequenceDiagram
    participant U as User
    participant C as Client
    participant S as Server
    participant DB as Database

    U->>C: 로그인 요청
    C->>S: POST /api/login
    S->>DB: 사용자 조회
    DB-->>S: 사용자 정보
    S-->>C: JWT 토큰
    C-->>U: 로그인 성공
```

**화살표 종류:**
- `->` : 실선
- `-->` : 점선
- `->>` : 실선 + 화살표
- `-->>` : 점선 + 화살표

---

### Class Diagram (클래스 다이어그램)

```mermaid
classDiagram
    class User {
        +String name
        +String email
        +login()
        +logout()
    }
    class Order {
        +int orderId
        +Date orderDate
        +calculate()
    }
    class Product {
        +String name
        +float price
    }

    User "1" --> "*" Order : places
    Order "*" --> "*" Product : contains
```

---

### State Diagram (상태 다이어그램)

```mermaid
stateDiagram-v2
    [*] --> Idle
    Idle --> Processing : 요청 수신
    Processing --> Success : 처리 완료
    Processing --> Error : 처리 실패
    Success --> [*]
    Error --> Idle : 재시도
```

---

### ERD (Entity Relationship Diagram)

```mermaid
erDiagram
    USER ||--o{ ORDER : places
    ORDER ||--|{ ORDER_ITEM : contains
    PRODUCT ||--o{ ORDER_ITEM : "ordered in"

    USER {
        int id PK
        string name
        string email
    }
    ORDER {
        int id PK
        int user_id FK
        date created_at
    }
    PRODUCT {
        int id PK
        string name
        decimal price
    }
```

---

### Gantt Chart (간트 차트)

```mermaid
gantt
    title 프로젝트 일정
    dateFormat  YYYY-MM-DD
    section 기획
    요구사항 분석    :a1, 2026-01-01, 7d
    설계 문서 작성   :a2, after a1, 5d
    section 개발
    백엔드 개발      :b1, after a2, 14d
    프론트엔드 개발  :b2, after a2, 14d
    section 테스트
    통합 테스트      :c1, after b1, 7d
```

---

### Pie Chart (파이 차트)

```mermaid
pie title 기술 스택 비율
    "JavaScript" : 40
    "Python" : 30
    "Java" : 20
    "기타" : 10
```

---

## 테마 설정

Mermaid는 다양한 테마를 지원합니다. `mermaid.initialize()`에서 설정할 수 있습니다:

```javascript
mermaid.initialize({
  startOnLoad: false,
  theme: 'dark'  // default, dark, forest, neutral
});
```

---

## 마무리

Mermaid를 사용하면 복잡한 다이어그램도 텍스트로 쉽게 작성할 수 있습니다. Git으로 버전 관리가 되고, 수정도 간편합니다. 기술 문서나 블로그 포스트에 적극 활용해보세요.

**참고 자료:**
- [Mermaid 공식 문서](https://mermaid.js.org/intro/)
- [Mermaid Live Editor](https://mermaid.live/) - 실시간 미리보기

---
*Originally published at [https://dss99911.github.io](https://dss99911.github.io/frontend/common/2026/01/20/mermaid-diagram-jekyll.html)*

Platform Debugger Architecture Redesign for Remote Development in 2026.1

The IntelliJ Platform provides a powerful debugger framework that plugins can extend to support various programming languages and runtime environments. To enable remote development scenarios – where the IDE interface runs on your local machine while code executes on a remote server – we’ve redesigned the debugger architecture. This guide walks you through the changes and shows you how to migrate your plugin for 2026.1 and beyond.

Why split the debugger?

In the last several releases, we re‑architected the IntelliJ Platform debugger to support Remote Development mode. The major change visible to users is that the Debugger tool window and breakpoints are now rendered on the frontend, while the backend hosts the active debugger session and communicates with the target process. As a result, the debugger experience in Remote Development mode is much more responsive and stable. However, these changes have modified the debugger implementation and might affect existing plugins.

To provide the same experience in both Remote Development and Local IDE modes, we use a single implementation for both. Therefore, the changes described in this guide can affect plugins in both modes.

Important: This guide focuses primarily on plugin support in Local mode. For migration steps for Remote Development mode, see the section below.

What’s changed?

To support Remote Development mode, we split the debugger into two parts: frontend and backend. The frontend is responsible for rendering the Debugger tool window (frames, variables’ tree, inlays, etc.). The backend hosts the active debugger session started by a plugin and provides data to the frontend via RPC (Remote Procedure Call).

As a result of these changes, several debugger API contracts have changed.

  1. The debugger UI is now created asynchronously, so older methods that access the debugger UI or the Debugger tab may cause race conditions.
  2. The frontend implementation no longer works directly with entities provided by plugins via XDebugProcess and XSuspendContext. For example, XStackFrame, XValue, and other instances are no longer available on the frontend. Instead, we use FrontendXValue wrappers that cannot be cast to plugin‑specific XValue implementations.

Example: Suppose you have a plugin that implements a custom debugger. The plugin extends XDebugProcess as MyDebugProcess and provides custom implementations: MyStackFrame (which extends XStackFrame), MyValue (which extends XValue), and other entities. In addition to the backend implementation, the plugin provides an action that assumes the platform debugger UI operates on those entities directly (e.g. XValueNodeImpl contains MyValue).

Now that the debugger has been split, the platform debugger UI operates with FrontendXValue and other frontend wrappers instead (e.g. XValueNodeImpl now contains FrontendXValue). As a result, actions operating on the debugger UI may unexpectedly access frontend entities and fail to cast them to plugin entities (e.g. FrontendXValue is not an instance of MyValue). See the following sections for more details.

What should I do?

Test your plugin with the 2026.1 EAP. UI components and actions are the areas most likely to be affected.

Pay attention to error messages in the IDE log. Incorrect debugger API usage will be logged with the [Split debugger] message prefix and an explanation of the migration steps.

Two migration paths:

Depending on your plugin requirements, choose one of the following migration paths:

  1. Local mode only – Migrate from the affected APIs below to the recommended ones. Your plugin will work in Local IDE mode, but some functionality may work incorrectly in Remote Development.
  2. Local + Remote Development modes – Follow the Remote Development mode migration steps below to support both modes.

If you encounter unexpected API behavior, please report it in YouTrack.

Which APIs are affected in Local IDE mode?

The following APIs related to the debugger UI are deprecated and unsafe to use since 2026.1:

  • com.intellij.xdebugger.XDebugSession#getRunContentDescriptor – deprecated and must not be used, as the frontend UI should not be accessed from the backend. See the Javadoc for more details.
  • com.intellij.xdebugger.XDebugSession#getUI – deprecated and must not be used. Can lead to race conditions due to asynchronous creation of the frontend UI.

Workaround: Use com.intellij.xdebugger.impl.XDebugSessionImpl.runWhenUiReady.

  • com.intellij.xdebugger.impl.XDebugSessionImpl#getSessionTab (internal) – deprecated and must not be used. Can also lead to race conditions.

Workaround: Use com.intellij.xdebugger.impl.XDebugSessionImpl.runWhenTabReady.

Which APIs are safe to use in Local IDE mode?

All APIs that provide data to the backend are valid and safe to use in Local mode, such as XDebugProcess, XSuspendContext, and the data they provide.

For creating debugger‑related actions, the following APIs are supported:

  • com.intellij.xdebugger.impl.actions.handlers.XDebuggerActionHandler
  • com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase
  • com.intellij.xdebugger.impl.ui.tree.actions.XFetchValueActionBase

These actions should not require changes in Local IDE mode. They have been adapted to use plugin‑specific entities instead of frontend wrappers to preserve compatibility with the older implementation.

Remote Development mode migration steps

The following APIs will NOT work in Remote Development mode:

  • UI and Debugger tab access:
    • com.intellij.xdebugger.XDebugSession#getUI
    • com.intellij.xdebugger.impl.XDebugSessionImpl#getSessionTab
    • com.intellij.xdebugger.impl.XDebugSessionImpl.runWhenTabReady
  • Base action classes that work with debugger nodes:
    • XDebuggerTreeActionBase
    • XFetchValueActionBase

Use these alternatives instead:

  • com.intellij.xdebugger.XDebugProcess.createTabLayouter – use this to provide additional tabs in the Debugger tool window.
  • com.intellij.xdebugger.impl.XDebugSessionImpl.runWhenUiReady – also works for adding tabs, but com.intellij.xdebugger.XDebugProcess.createTabLayouter is the recommended approach.
  • XDebuggerTreeBackendOnlyActionBase – an alternative to XDebuggerTreeActionBase that works only with XValue instead of nodes.