Navigation: Docs index · Getting started · API reference
This guide helps you diagnose and resolve common issues when using nostr-java.
- Installation Issues
- Connection Problems
- Authentication & Signing Issues
- Event Publishing Issues
- Subscription Issues
- Encryption & Decryption Issues
- Performance Issues
- Integration Testing Issues
Symptom: Maven or Gradle cannot resolve xyz.tcheeric:nostr-java-client:<version>
Solution: Ensure you've added the custom repository to your build configuration:
Maven:
<repositories>
<repository>
<id>nostr-java</id>
<url>https://maven.398ja.xyz/releases</url>
</repository>
</repositories>Gradle:
repositories {
maven { url 'https://maven.398ja.xyz/releases' }
}Symptom: UnsupportedClassVersionError or compilation errors
Solution: nostr-java requires Java 21 or higher. Verify your Java version:
java -versionIf needed, update your build configuration:
Maven (pom.xml):
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>Gradle (build.gradle):
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}Symptom: ClassNotFoundException or NoSuchMethodError at runtime
Solution: Check for dependency conflicts, especially with Spring WebSocket or JSON libraries:
# Maven
mvn dependency:tree
# Gradle
gradle dependenciesExclude conflicting transitive dependencies if needed (version managed by the BOM):
<dependency>
<groupId>xyz.tcheeric</groupId>
<artifactId>nostr-java-client</artifactId>
<exclusions>
<exclusion>
<groupId>conflicting-group</groupId>
<artifactId>conflicting-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>Symptom: IOException, ConnectException, or timeouts when connecting to relay
Possible Causes & Solutions:
Ensure the relay URL uses the correct WebSocket protocol:
- Use
wss://for secure connections (recommended) - Use
ws://only for local development (e.g.,ws://localhost:5555)
Bad:
new NostrRelayClient("https://relay.398ja.xyz"); // Wrong protocolGood:
new NostrRelayClient("wss://relay.398ja.xyz");Test the relay URL independently:
# Using websocat (install: cargo install websocat)
websocat wss://relay.398ja.xyz
# Or use an online WebSocket testerTry alternative public relays:
wss://relay.398ja.xyzwss://nos.lolwss://relay.nostr.band
If behind a corporate firewall, configure proxy settings:
System.setProperty("https.proxyHost", "proxy.example.com");
System.setProperty("https.proxyPort", "8080");Symptom: Subscription stops receiving events after a period
Solution: Use retry and error handling with NostrRelayClient:
try (NostrRelayClient client = new NostrRelayClient("wss://relay.398ja.xyz")) {
AutoCloseable subscription = client.subscribe(
req,
message -> System.out.println(message),
error -> {
System.err.println("Connection error: " + error.getMessage());
// NostrRelayClient uses Spring Retry with exponential backoff
},
() -> System.out.println("Connection closed")
);
}Symptom: Relay rejects event with signature error
Possible Causes:
Ensure you sign the event before sending:
Bad:
GenericEvent event = GenericEvent.builder()
.pubKey(identity.getPublicKey())
.kind(Kinds.TEXT_NOTE)
.content("Hello")
.build();
client.send(new EventMessage(event)); // Missing signature!Good:
GenericEvent event = GenericEvent.builder()
.pubKey(identity.getPublicKey())
.kind(Kinds.TEXT_NOTE)
.content("Hello")
.build();
identity.sign(event); // Sign first
client.send(new EventMessage(event));Never modify an event after signing it. Any change invalidates the signature.
Ensure private keys are in the correct format (32-byte hex string, 64 hex characters):
String validKey = "a".repeat(64);
Identity id = Identity.create(validKey);Debugging Steps:
GenericEvent event = GenericEvent.builder()
.pubKey(identity.getPublicKey())
.kind(Kinds.TEXT_NOTE)
.content("Hello")
.build();
identity.sign(event);
// Log the event JSON
String json = new EventMessage(event).encode();
System.out.println("Sending event: " + json);List<String> responses = client.send(new EventMessage(event));
responses.forEach(response ->
System.out.println("Relay response: " + response)
);Symptom: RelayTimeoutException when sending events
Solution: Increase the timeout or check relay connectivity:
// Increase timeout to 2 minutes
NostrRelayClient client = new NostrRelayClient("wss://relay.398ja.xyz", 120_000);Or configure via Spring properties:
nostr.websocket.await-timeout-ms=120000Debugging Steps:
// Too restrictive — might match nothing
EventFilter tooRestrictive = EventFilter.builder()
.authors(List.of(specificPubKey))
.kinds(List.of(Kinds.TEXT_NOTE))
.since(Instant.now().getEpochSecond()) // Only future events
.build();
// More permissive — should match events
EventFilter permissive = EventFilter.builder()
.kinds(List.of(Kinds.TEXT_NOTE))
.limit(10)
.build();// Most widely supported
EventFilter basic = EventFilter.builder()
.kinds(List.of(Kinds.TEXT_NOTE))
.limit(5)
.build();Symptom: Application becomes unresponsive or slow
Note: In nostr-java 2.0, subscription callbacks are dispatched on Virtual Threads, so blocking in callbacks does not block WebSocket I/O. However, for high-throughput feeds, consider using a bounded queue:
BlockingQueue<String> eventQueue = new LinkedBlockingQueue<>(1000);
client.subscribe(req,
message -> {
if (!eventQueue.offer(message)) {
System.err.println("Queue full, dropping event");
}
},
error -> System.err.println(error),
() -> {}
);
// Process from queue at controlled rate
while (running) {
String message = eventQueue.poll(1, TimeUnit.SECONDS);
if (message != null) processMessage(message);
}Symptom: NostrException or garbled plaintext
Possible Causes:
Ensure you're using the recipient's private key to decrypt:
Identity alice = Identity.generateRandomIdentity();
Identity bob = Identity.generateRandomIdentity();
// Alice encrypts for Bob
MessageCipher04 cipher = new MessageCipher04(alice.getPrivateKey(), bob.getPublicKey());
String encrypted = cipher.encrypt("Secret message");
// Bob decrypts (using his private key + Alice's public key)
MessageCipher04 bobCipher = new MessageCipher04(bob.getPrivateKey(), alice.getPublicKey());
String decrypted = bobCipher.decrypt(encrypted);Solution: Match encryption and decryption versions:
// NIP-04 (legacy)
MessageCipher04 cipher04 = new MessageCipher04(senderPriv, recipientPub);
String encrypted04 = cipher04.encrypt("Hello");
// NIP-44 (recommended)
MessageCipher44 cipher44 = new MessageCipher44(senderPriv, recipientPub);
String encrypted44 = cipher44.encrypt("Hello");
// Can't mix: cipher04.decrypt(encrypted44) will fail!Solutions:
NostrRelayClient.connectAsync("wss://relay.398ja.xyz")
.thenCompose(client -> client.sendAsync(new EventMessage(event)))
.thenAccept(responses -> System.out.println("Done: " + responses));Solutions:
EventFilter filter = EventFilter.builder()
.kinds(List.of(Kinds.TEXT_NOTE))
.limit(100)
.build();AutoCloseable subscription = client.subscribe(/* ... */);
try {
// Use subscription
} finally {
subscription.close(); // Always close!
}Solution: Use strfry relay instead of nostr-rs-relay:
# src/test/resources/relay-container.properties
relay.container.image=dockurr/strfry:latest
relay.container.port=7777Possible Causes:
- Docker not available: Skip tests when Docker is unavailable:
@DisabledIfSystemProperty(named = "noDocker", matches = "true")- Resource constraints: Use tmpfs for relay storage:
.withTmpFs(Map.of("/app/strfry-db", "rw"))If your issue isn't covered here:
- Check the API reference: reference/nostr-java-api.md
- Review examples: howto/api-examples.md
- Search existing issues: GitHub Issues
- Open a new issue: Provide nostr-java version, Java version, minimal reproducing code, and full stack trace.
For Spring Boot applications, add to application.properties:
logging.level.nostr=DEBUG
logging.level.nostr.client=TRACE