MY WEBCAM TEST GUIDE

How to Create an HTTPS Server in Java for Camera and Microphone Testing

Build a small HTTPS server in Java with the JDK's built-in HttpsServer and a keystore, then confirm the browser treats your page as a secure context for camera access.

The short answer

The JDK includes an HTTPS server, so you don't need a framework. You create a certificate in a keystore with the keytool utility, load it into an SSLContext, and pass that context to com.sun.net.httpserver.HttpsServer. This server is a good fit for local tests and simple pages, such as a camera or microphone check page. For a public, production service, a full web framework or a reverse proxy that handles TLS is usually a better choice.

If you want HTTPS so a browser will allow camera or microphone access, check one thing first. Browsers only expose camera and microphone APIs in a secure context. A secure context is a page served over HTTPS, or a page served from the local machine itself (localhost). If you open the page only on the same computer through localhost, plain HTTP may already be enough. You need HTTPS when other devices open the page, such as a phone on your home network reaching your computer by its IP address, or when the page is public.

Step 1: Create a keystore with a certificate

A keystore is a file that holds the server's private key and certificate. The command below creates a self-signed certificate for local use in the PKCS12 format. The SAN (Subject Alternative Name) entries list the names and addresses the certificate is valid for. Browsers check these entries, so add your computer's local IP address as well if other devices will connect to it. Replace the password placeholder with your own value.

keytool -genkeypair \
  -alias localdev \
  -keyalg RSA -keysize 2048 \
  -validity 365 \
  -storetype PKCS12 \
  -keystore keystore.p12 \
  -storepass YOUR_PASSWORD \
  -dname "CN=localhost" \
  -ext "SAN=dns:localhost,ip:127.0.0.1"

If you already have a certificate from a certificate authority, convert it and its private key into a PKCS12 file and use that file instead of generating a new one.

Step 2: Write the server

The program below loads the keystore, builds an SSLContext, and starts an HTTPS server on port 8443. It serves one small test page that reports whether the browser sees a secure context and then asks for camera access. The keystore password is read from an environment variable so it stays out of the source code. The port is only an example; you can choose another one.

import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;

public class SimpleHttpsServer {

    private static final String PAGE =
        "<!doctype html><html><body>"
        + "<p id='status'></p>"
        + "<video id='preview' autoplay playsinline muted></video>"
        + "<script>"
        + "const s = document.getElementById('status');"
        + "s.textContent = 'Secure context: ' + window.isSecureContext;"
        + "if (!navigator.mediaDevices) {"
        + "  s.textContent += ' | Camera API not available on this page';"
        + "} else {"
        + "  navigator.mediaDevices.getUserMedia({ video: true })"
        + "    .then(stream => { document.getElementById('preview').srcObject = stream; })"
        + "    .catch(err => { s.textContent += ' | Camera error: ' + err.name; });"
        + "}"
        + "</script></body></html>";

    public static void main(String[] args) throws Exception {
        char[] password = System.getenv("KEYSTORE_PASSWORD").toCharArray();

        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (FileInputStream in = new FileInputStream("keystore.p12")) {
            keyStore.load(in, password);
        }

        KeyManagerFactory kmf =
            KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(keyStore, password);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), null, null);

        HttpsServer server = HttpsServer.create(new InetSocketAddress(8443), 0);
        server.setHttpsConfigurator(new HttpsConfigurator(sslContext));

        server.createContext("/", exchange -> {
            byte[] body = PAGE.getBytes(StandardCharsets.UTF_8);
            exchange.getResponseHeaders().set("Content-Type", "text/html; charset=utf-8");
            exchange.sendResponseHeaders(200, body.length);
            try (OutputStream out = exchange.getResponseBody()) {
                out.write(body);
            }
        });

        server.start();
        System.out.println("HTTPS server started on port 8443");
    }
}

Step 3: Run it

Put the keystore file in the directory you run the program from, set the password variable, and start the program. On recent JDK versions you can run a single source file directly without compiling it first.

export KEYSTORE_PASSWORD=YOUR_PASSWORD
java SimpleHttpsServer.java

In the browser, open the page with the https scheme, host localhost and port 8443. From another device, use your computer's IP address instead of localhost. That IP address must be listed in the certificate's SAN entries, and your firewall must allow the port.

Step 4: Verify the result

Each of the checks below confirms a different thing. Passing one does not prove the next.

  • The page loads over HTTPS. This confirms the server and certificate work, even if the browser shows a warning about an untrusted certificate.
  • The status line shows "Secure context: true". This confirms the browser will offer camera and microphone APIs to the page.
  • The browser asks for camera permission. Allowing it grants access, but it does not yet prove that video is coming through.
  • A live image appears in the video area. This confirms the camera is actually capturing video. It says nothing about how the video will look or perform in a real call, which depends on lighting, the network and the calling app.

If an error name appears instead of video, it usually points to the cause. NotAllowedError means permission was denied, either by you or by a browser or operating-system setting. NotFoundError means no camera was found. NotReadableError usually means the camera exists but another app or the system is blocking it. To test a microphone, change the request to ask for audio as well as video. A granted microphone permission still only shows the device was detected. You need a separate check to confirm that it picks up sound and that the sound is clear.

Limitations to keep in mind

Browsers don't trust a self-signed certificate by default, so they show a warning. For your own testing, you can accept the warning or add the certificate to your system or browser trust store. Browsers handle an accepted warning differently, and the material this guide is based on doesn't say how each one treats camera permissions in that case. If the status line shows a secure context but the permission prompt never appears, add the certificate to the trust store and test again. For a page other people will use, get a certificate from a recognized certificate authority instead of using a self-signed one.

The built-in server handles HTTPS correctly, but it is minimal. It has no routing for static files, no automatic certificate renewal and no hardening for heavy public traffic. Use it to prove that the secure-context requirement is met, then move to a framework or a TLS-terminating proxy if the page becomes a real service.