Repository navigation
Server Configuration
Configure the network boundary and give each server resource one lifecycle owner.
Milo 1.2.0-SNAPSHOT, Java 17. API baseline 7faaf4f0516c.
Use milo-sdk-server with the matching OPC TCP transport dependencies described in Getting started. Your first server supplies a complete local program. Choose the application URI, advertised hosts, security policies, and authentication rules before exposing a listener. A server application URI is a persistent identity and must match the URI in its application certificate when using security.
An EndpointConfig describes a transport profile, bind address and port, advertised hostname and path, security policy/mode, a fixed certificate or a certificate selection request, and supported user-token policies. Supply the set through OpcUaServerConfig.builder().setEndpoints(...); create OpcUaServer with a transport factory that supplies the transport for each configured profile.
The bind address selects a local network interface. The advertised hostname is what clients discover and connect to. The listener and advertised URL share bindPort; there is no separate advertised-port setting. Defaults are bind address localhost, hostname localhost, and port 12685. A NAT or container port translation therefore needs equal internal/external ports or a deliberate client URL override; see Deployment. An all-interface bind address is not an appropriate advertised destination. Advertise names that clients can resolve and route to, and include those names in the server certificate SANs. NAT, containers, DNS, and firewalls remain deployment responsibilities. Several endpoint descriptions can share one listening socket while offering different security modes or token policies.
Use OPC TCP for this guide. The repository's HTTP/WebSocket experiments are not a deployment alternative; see Capabilities and limitations. After startup, getBoundEndpoints() identifies configurations actually bound. server.getApplicationContext().getEndpointDescriptions() represents advertisements, which may change after certificate updates. These sets are not interchangeable: resetEndpointDescriptionCache() recalculates advertisements and does not bind a new listening socket.
The default identity validator is AnonymousIdentityValidator.INSTANCE, which accepts only anonymous tokens. No role mapper is installed by default; install one through setRoleMapper(...) when using role checks. Advertise only user-token policies that the configured identity validator accepts. AnonymousIdentityValidator, UsernameIdentityValidator, X509IdentityValidator, and CompositeValidator validate user identity; Access control determines that user's operations. The application certificate authenticates the application, not the human or service account using a Session. Avoid copying the demonstration passwords or permissive X.509 predicate from ExampleServer into a deployment.
Use setLimits(OpcUaServerConfigLimits) to choose limits such as operations per request, Sessions, subscriptions, monitored items, and sampling/publishing rates. Set limits from application capacity and test the revised values returned to clients. Increasing an operation limit does not supply more device throughput or memory. Service-level limits can reject a whole request; per-node errors remain in result arrays.
Without explicit worker resources, the configuration uses the process-wide daemon pools Stack.sharedExecutor() and Stack.sharedScheduledExecutor(). server.shutdown() does not close them. See Configuration and Deployment for coordinated final process cleanup. setExecutor(...) and setScheduledExecutor(...) accept application-supplied worker resources. Bound I/O and method execution; a blocking device read or authorization filter can consume workers needed by other clients. Track and close executors you create after server work has stopped. Coordinate shared executors with all users before closing them.
The following fragment configures a secured username endpoint with a fixed certificate. builder is an OpcUaServerConfigBuilder; port is an available local port; applicationUri, certificate, and certificateManager are matching provisioned identities as described in Server security. usernameValidator is an application-supplied UsernameIdentityValidator that checks credentials against your credential store. The fixture uses local test credentials and explicitly trusts both application certificates.
EndpointConfig endpoint =
EndpointConfig.newBuilder()
.setBindAddress("127.0.0.1")
.setHostname("localhost")
.setBindPort(port)
.setPath("/wiki")
.setCertificate(certificate)
.setSecurityPolicy(SecurityPolicy.Basic256Sha256)
.setSecurityMode(MessageSecurityMode.SignAndEncrypt)
.addTokenPolicy(OpcUaServerConfig.USER_TOKEN_POLICY_USERNAME)
.build();
builder
.setApplicationUri(applicationUri)
.setCertificateManager(certificateManager)
.setEndpoints(Set.of(endpoint))
.setIdentityValidator(usernameValidator)
.setLimits(
new OpcUaServerConfigLimits() {
@Override
public UInteger getMaxNodesPerRead() {
return uint(2);
}
});setCertificate(certificate) advertises that fixed certificate; its identity and key pair must be available in the certificate manager. To follow certificate rotation, omit setCertificate(...). This secure endpoint then selects a compatible identity from the DefaultApplicationGroup. To select a different registered group or request a certificate type, use EndpointConfig.Builder.setEndpointCertificateConfig(EndpointCertificateConfig) without a fixed certificate. See Server security.
The fragment uses EndpointConfig, OpcUaServerConfig, and OpcUaServerConfigLimits from org.eclipse.milo.opcua.sdk.server, SecurityPolicy from org.eclipse.milo.opcua.stack.core.security, MessageSecurityMode from org.eclipse.milo.opcua.stack.core.types.enumerated, UInteger from org.eclipse.milo.opcua.stack.core.types.builtin.unsigned, static Unsigned.uint, and java.util.Set. Discovery advertises localhost with the configured port and username token policy. For this encrypted Basic256Sha256 username-token path, a rejected credential fails activation with Bad_IdentityTokenInvalid; a valid credential establishes the Session. Other username-token paths can return Bad_UserAccessDenied, so inspect the actual activation result. A request reading three Values exceeds this deliberately small limit and fails with Bad_TooManyOperations. Disconnect clients after failed connection attempts as well as successful ones.
Create managed namespaces and register them before startup. In this lifecycle fragment, server is configured to listen for inbound connections and namespace is an instance of your ManagedNamespaceWithLifecycle subclass. The same ownership pattern applies to other Lifecycle participants. Import java.util.concurrent.TimeUnit.
The bound-endpoint guard requires an inbound listener. Omit it for a Reverse Connect-only server, which can start with no bound endpoint when at least one target is schedulable. Inspect its targets and connection progress as described in Reverse Connect.
server.addLifecycleParticipant(namespace);
try {
server.startup().get(10, TimeUnit.SECONDS);
if (server.getBoundEndpoints().isEmpty()) {
throw new IllegalStateException("server has no bound endpoint");
}
// Application work runs here while the server owns the namespace lifecycle.
} finally {
server.shutdown().get(10, TimeUnit.SECONDS);
}The example deliberately performs only a startup/shutdown smoke cycle and checks that at least one inbound endpoint bound. For a server without Reverse Connect targets, startup already fails when nothing binds, so this guard is redundant. It does not preserve a startup exception if shutdown also fails; production error reporting should retain both, for example with a suppressed cleanup exception. A service application does its work, or awaits its own shutdown signal, inside the try. Wait for successful startup before reporting readiness. Do not also call namespace.startup() or namespace.shutdown() after transferring ownership to the server.
Startup does not require every configured endpoint. A secure endpoint is omitted with a warning if its security provider is unusable. Secure endpoints are also omitted when managed selection finds no compatible identity or an enhanced policy rejects an incompatible fixed certificate; an individual bind failure is also logged without necessarily failing startup. Startup fails with Bad_ConfigurationError when nothing binds and no Reverse Connect target is schedulable, or when Session-capable endpoint selection keys collide. Before reporting readiness for inbound connections, compare getBoundEndpoints() with the listening endpoints you require, especially secured endpoints. A None endpoint can bind successfully while an unusable secure endpoint is omitted. Advertisements may still include configurations whose socket binding failed.
Participants start in registration order after core facilities are ready and before listening. If startup fails, only participants whose startup completed normally receive rollback shutdown, in reverse order. A participant that throws must clean up its own partially allocated resources. Registering twice or registering after startup begins is an error.
Shutdown rejects new Sessions, stops Reverse Connect activity, unbinds endpoints, closes Sessions, and tears down application participants before the standard address space. All started participants get a cleanup attempt; cleanup failures complete the shutdown future exceptionally and later failures are suppressed. Preserve both startup and cleanup failures in production error reporting. A timeout or cancellation of a returned future does not cancel the underlying lifecycle work. A participant may request shutdown from its startup callback but must not wait for the returned future. Before startup, removeLifecycleParticipant(...) transfers ownership back to the application.
A server instance is terminal after failure or shutdown. Repeated startup() can return its original future; it does not restart the listener. Build a fresh server and fresh owned resources to restart.
User guide for Milo 1.2.0-RC1 at source 69cb25087, 2026-10-12. Home · Getting started · Examples · Troubleshooting · Release notes
- Home
- Start here
- Client guide
- Server guide
- Feature guides
- Reference
- Operations
- Examples
- Release notes
- Contributing