apple/pkl · error · ProtocolException

Unexpected end of input; 0 message bytes

Error message

Unexpected end of input; 0 message bytes

What it means

NativeTransport.sendMessage decodes a message received from the native Pkl evaluator over the MessagePack IPC transport. The library throws this ProtocolException when the native side hands over a message whose declared length is 0, meaning there are no message bytes to decode at all. This signals a broken or prematurely closed protocol exchange between the JVM and the native evaluator rather than a decodable (even malformed) message.

Source

Thrown at libpkl/src/main/java/org/pkl/libpkl/NativeTransport.java:64

  protected void doSend(Message message) throws ProtocolException {
    try (var os = new ByteArrayOutputStream();
        var packer = MessagePack.newDefaultPacker(os)) {
      var encoder = new ServerMessagePackEncoder(packer);
      encoder.encode(message);
      sendMessageToNative.accept(os.toByteArray());
    } catch (IOException e) {
      // impossible; no IO happens during packing
      throw PklBugException.unreachableCode();
    } catch (ProtocolException e) {
      // should never happen; messages coming from Pkl should always be well-formed.
      log("Unexpected protocol exception: " + e);
      throw e;
    }
  }

  public void sendMessage(int length, CCharPointer ptr) throws ProtocolException {
    if (length == 0) {
      throw new ProtocolException("Unexpected end of input; 0 message bytes");
    }
    try (var is = new NativeInputStream(length, ptr);
        var unpacker = MessagePack.newDefaultUnpacker(is)) {
      var message = new ServerMessagePackDecoder(unpacker).decode();
      // guaranteed by `length == 0` check above
      assert message != null;
      accept(message);
    } catch (IOException e) {
      // impossible; no IO happens during unpacking
      throw PklBugException.unreachableCode();
    }
  }
}

View on GitHub (pinned to f3efcbfc9b)

Solutions

  1. Check that the native Pkl evaluator process is still alive and inspect its stderr/stdout logs for a crash just before this error.
  2. Verify the libpkl native library version matches the org.pkl.libpkl Java bindings version.
  3. Ensure no code closes or cancels the evaluator concurrently with pending requests; serialize close after all messages are sent.
  4. Enable verbose transport/protocol logging to capture the last successful frame before the empty one.
  5. Retry the evaluation with a fresh Evaluator instance if the native side died.

Example fix

// before: sending a message with a null/empty payload buffer
transport.sendMessage(0, null);
// after: guard and only send when a real payload exists
if (payloadBytes == 0 || ptr == null) {
  throw new IllegalStateException("no encoded message to send");
}
transport.sendMessage(payloadBytes, ptr);
Defensive patterns

Strategy: try-catch

Validate before calling

// before consuming the transport, verify the evaluator is alive
if (!transport.isOpen() || evaluatorHandle == null) {
  throw new IllegalStateException("native evaluator not started");
}
if (length <= 0) {
  log.error("native peer sent empty frame; evaluator likely crashed");
}

Type guard

static boolean hasPayload(int length, CCharPointer ptr) {
  return length > 0 && ptr != null;
}

Try / catch

try {
  transport.sendMessage(length, ptr);
} catch (ProtocolException e) {
  log.error("broken native transport: {}", e.getMessage(), e);
  evaluator.close(); // native side is unusable; rebuild the evaluator
  throw new EvaluationUnavailableException(e);
}

Prevention

When it happens

Trigger: Calling sendMessage (or the underlying evaluator request path it serves) when the native peer passes length==0 into the callback — i.e. the native evaluator produced an empty frame, was shut down mid-conversation, or the transport was misinitialized.

Common situations: The Pkl native binary crashed or exited while the Java evaluator was still exchanging messages; a version mismatch between libpkl native library and the Java bindings; evaluator close/cancel racing an in-flight request.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08). Data as JSON: /api/errors/7ec575437cbf0579. Report an issue: GitHub.