Java에서 .NET 코드 호출하기: 모든 접근 방식 순위 (모두 경험한 사람에 의해)
Source: Dev.to
일반적인 시나리오
- Legacy C# pricing engine – 회사는 재작성하지 않음
- .NET‑specific libraries – Java에 해당하는 것이 없음
- Windows APIs – Java 애플리케이션이 갑자기 이를 호출해야 함
- .NET service – 너무 밀접하게 결합돼 있어 깔끔한 API로 래핑하기 어려움
본능적으로 “왜 Java로 다시 작성하지 않나요?” 라고 묻게 됩니다.
답은 보통 시간과 비용이며, 그래서 통합하게 됩니다.
1. .NET 코드를 ASP.NET Core Web API로 래핑하기
C# 측
[ApiController]
[Route("api/[controller]")]
public class PricingController : ControllerBase
{
[HttpPost("calculate")]
public ActionResult Calculate([FromBody] PriceRequest request)
{
var engine = new PricingEngine();
return Ok(engine.CalculatePrice(request));
}
}
Java 측
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:5000/api/pricing/calculate"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
다음 상황에서 잘 작동합니다
- 사용자 요청당 몇 번의 호출만 수행할 때.
- 명확한 분리; 팀이 독립적으로 작업할 수 있음.
- 배포 및 모니터링이 쉬움.
다음 상황에서 문제가 발생합니다
- 하나의 비즈니스 작업에 많은 .NET 메서드 호출이 필요할 때.
- 예: 단일 가격 계산에 10‑15개의 개별 C# 호출이 필요함.
- HTTP 라운드 트립당 약 20 ms라면, 실제 계산이 시작되기 전 순수 네트워크 오버헤드만 200‑300 ms가 소요됩니다.
2. Binary Serialization over HTTP/2 (gRPC)
REST보다 빠르며, Protocol Buffers를 통한 강력한 타입 계약을 제공합니다.
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 5001)
.usePlaintext()
.build();
PricingServiceGrpc.PricingServiceBlockingStub stub =
PricingServiceGrpc.newBlockingStub(channel);
PriceResult result = stub.calculatePrice(
PriceRequest.newBuilder()
.setProductId("WIDGET-123")
.build());
Improvement over REST
- 호출당 대략 5‑15 ms이며, 25‑75 ms 대신입니다.
- 타입이 지정된 계약으로 컴파일 시점에 깨지는 변경을 잡아내어, 운영 중에 문제를 발견하는 것을 방지합니다.
Still limited by
- 네트워크 지연. 호출 패턴이 많은 라운드‑트립을 요구한다면 여전히 밀리초가 누적됩니다.
- 이제 .proto files를 양쪽 코드베이스에 걸쳐 관리해야 합니다.
3. C++로 .NET CLR을 로드하고 JNI를 통해 Java에 노출하기
솔직히 말하자면, 우리는 팀들이 이 작업을 시도했다가 몇 주 만에 포기하는 것을 보았습니다.
JNI, C++ 메모리 관리, 그리고 .NET 호스팅 API의 조합은 디버깅 악몽을 만듭니다. 하나의 잘못 관리된 참조만으로도 최소한의 진단 정보로 전체 JVM이 충돌할 수 있습니다.
- 의미가 있을 때 – 매우 특수한 임베디드 시스템.
- 일반적인 기업 통합 – 엔지니어링 비용이 거의 정당화되지 않습니다.
4. Run .NET as a Subprocess from Java
ProcessBuilder pb = new ProcessBuilder(
"dotnet", "run",
"--project", "PricingEngine",
"--", "WIDGET-123", "100"
);
Process process = pb.start();
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String result = reader.readLine();
Good for
- 배치 작업, 예약 작업, .NET을 자주 호출하지 않는 경우 등.
- 200 ms+ 시작 시간을 감수할 수 있는 시나리오.
Not good for
- 인터랙티브 워크로드. .NET 런타임 시작 비용이 호출당 발생하므로 사용자와 직접 상호작용하는 작업에는 실용적이지 않음.
5. 인‑프로세스 브리징 (JNBridge Pro가 하는 일)
전면 공개 — 이것이 JNBridge Pro가 하는 일입니다.
JVM과 CLR가 같은 프로세스 내에서 실행되며, 생성된 프록시 클래스 덕분에 .NET 객체를 네이티브 Java 객체처럼 사용할 수 있습니다.
// This is actually calling C# under the hood
PricingEngine engine = new PricingEngine();
PriceResult result = engine.calculatePrice("WIDGET-123", 100);
장점
- 마이크로초 수준의 호출 지연을 제공하며, 밀리초 수준보다 훨씬 빠릅니다.
- 작업당 수십 번의 런타임 간 호출이 필요한 워크로드에서는 성능 차이가 크게 나타납니다.
트레이드‑오프
- 배포가 복잡해집니다 (하나의 프로세스에 두 개의 런타임, 두 개의 가비지 컬렉터).
- 상용 라이선스 비용이 발생합니다.
- 성능 병목이 네트워크 오버헤드임을 측정하고 확인한 팀에 가장 적합합니다.
고객에게 사용을 권장하지 않을 때
- 요청당 2‑3번의 .NET 호출만 필요하다면, REST나 gRPC가 더 간단하고 사용자에게 느껴지는 지연 차이는 거의 없습니다.
- 필요하지 않은 경우 우리 제품을 구매하기보다 적절한 도구를 사용하는 것이 좋습니다.
6. 기타 오픈‑소스 / 상용 옵션
| Tool | Status | Notes |
|---|---|---|
| jni4net | 사실상 폐기됨 (≈2015) | 최신 .NET 버전을 지원하지 않음. |
| IKVM | 커뮤니티‑유지 포크 존재 | .NET 어셈블리를 Java 바이트코드로 컴파일하지만, 무거운 리플렉션이나 P/Invoke 사용 시 어려움이 있음. |
| Javonet | 상용 | 다른 기술적 접근 방식; 평가해볼 가치가 있음 – 경쟁은 생태계에 도움이 됨. |
7. 실제 배포에서의 수치
(페이로드 크기와 네트워크 상황에 따라 차이가 있을 수 있습니다.)
| 접근 방식 | 호출당 지연 시간 | 15회 호출(일반적인 복합 작업) |
|---|---|---|
| REST | 20‑50 ms | 300‑750 ms |
| gRPC | 5‑15 ms | 75‑225 ms |
| Process exec | 200 ms+ | 실용적이지 않음(호출당 프로세스 생성) |
| In‑process | <1 ms | ~10‑15 ms |
질문은 어떤 것이 가장 빠른가가 아니라 어떤 트레이드오프가 요구 사항에 맞는가 입니다.
8. 의사결정 가이드 – 세 가지 질문
-
사용자 요청당 교차 런타임 호출 수는?
- 1‑5 회 → REST 또는 gRPC
- 10회 이상 → 인‑프로세스 브리징 검토
-
지연 허용 범위는?
- 초 단위 → REST (가장 간단)
- 수백 ms → gRPC
- 수십 ms → 인‑프로세스
-
향후 어떻게 발전할 것인가?
- .NET에서 다른 플랫폼으로 마이그레이션 계획 → REST (나중에 교체하기 가장 쉬움)
- 두 플랫폼 모두 영구적 → 인‑프로세스 브리징에 투자 (예: JNBridge Pro, Javonet)
성능 요구사항, 운영 복잡성, 장기 전략에 맞는 접근 방식을 선택하세요.
더 긴밀한 통합
대부분의 팀은 REST 또는 gRPC를 선택하며, 이는 대부분의 아키텍처에 적합한 선택입니다.
통합 패턴에 대해 실제로 어떤 방식을 사용하고 있는지 듣고 싶습니다. Java/.NET 상호 운용 영역은 업계가 인식하는 것보다 더 흔하지만, 별로 흥미롭게 여겨지지 않기 때문에 크게 이야기되지 않습니다. 여러분의 설정을 댓글에 남겨 주세요.
Disclosure: 저는 JNBridge에서 근무하고 있으며, 여기서 논의되는 통합 도구 중 하나인 JNBridgePro를 개발합니다. 우리 제품이 최선이 아닌 경우도 포함하여 모든 접근 방식에 대해 솔직한 비교를 제공하려고 노력했습니다.