Error Handling and Security Features in Java

Error Handling

Aplications should catch org.xml.sax.SAXNotRecognizedException when setting new properties to ensure compatibility with older versions that may not support these properties. For instance, a method checkPropertySupport can determine if the current JDK version supports a specific property like JDK_GENERAL_ENTITY_SIZE_LIMIT:

public boolean checkPropertySupport() {
    try {
        SAXParser parser = createSAXParser();
        parser.setProperty(JDK_GENERAL_ENTITY_SIZE_LIMIT, "10000");
    } catch (ParserConfigurationException exc) {
        handleError(exc.getMessage());
    } catch (SAXException exc) {
        String message = exc.getMessage();
        if (message.contains("Property '" + JDK_GENERAL_ENTITY_SIZE_LIMIT + "' is not recognized.")) {
            System.out.println("New limit properties not supported. Skipping execution.");
            return false;
        }
    }
    return true;
}

When an input file contains structures that trigger limit exceptions, applications can examine error codes to identify the nature of the failure. The following error codes correspond to these limits:

  • EntityExpansionLimit: JAXP00010001
  • ElementAttributeLimit: JAXP00010002
  • MaxEntitySizeLimit: JAXP00010003
  • TotalEntitySizeLimit: JAXP00010004
  • MaxXMLNameLimit: JAXP00010005
  • maxElementDepth: JAXP00010006
  • EntityReplacementLimit: JAXP00010007

The error code format is:

"JAXP" + componentCode (two digits) + errorCategory (two digits) + sequenceNumber

Thus, code JAXP00010001 represents the JAXP basic parser security limit EntityExpansionLimit.

StAX

StAX, JSR 173, does not support FSP. However, the StAX implementation in the JDK supports the new limit properties and their corresponding system properties. This means that while there is no FSP to enable or disable limits, the described limits and system properties function identically.

For compatibility, StAX-specific properties always take precedence over new JAXP limits. For example, setting the SupportDTD property to false will cause an exception if the input file contains Entity references. Therefore, applications that use the SupportDTD property to disable DTD will not be affected by the addition of new limits.

Java Remote Method Invocation (RMI)

Java RMI enables objects in one Java virtual machine to invoke methods on objects running in another JVM, facilitating remote communication between programs written in Java.

Note: For connecting to existing IDL programs, use Java IDL instead of RMI.

RMI Application Overview

RMI applications typically consist of two separate programs: a server and a client. The server creates remote objects, makes references accessible, and waits for clients to invoke methods. The client obtains remote references to one or more remote objects on the server and calls their methods. RMI handles the communication mechanism between server and client. Such applications are often termed distributed object applications.

Distributed object applications must:

  • Locate remote objects. Applications can acquire references using various mechanisms, such as the RMI registry, or by passing remote object references as part of other remote calls.
  • Communicate with remote objects. RMI manages the details of remote communication, making remote method calls appear similar to regular Java method calls.
  • Load class definitions for passed objects. RMI allows objects to be passed and provides mechanisms to load class definitions along with object data.

Advantages of Dynamic Code Loading

A core feature of RMI is its ability to download class definitions if they are not defined in the recipient's JVM. This enables the transfer of an object's type and behavior to a remote JVM, dynamically extending application behavior.

Remote Interfaces, Objects, and Methods

Objects with methods callable across JVMs are remote objects. An object becomes remote by implementing a remote interface, which extends java.rmi.Remote. Each method in the interface declares java.rmi.RemoteException in its throws clause.

When a remote object is passed between JVMs, RMI transmits a remote stub instead of copying the implementation object. The stub acts as a local proxy, implementing the same set of remote interfaces as the remote object.

Steps to Create a Distributed Application with RMI

  1. Design and implement components. Define remote interfaces, implement remote objects, and develop clients.
  2. Compile source code. Use javac to compile remote interfaces, implementations, and client classes.
  3. Make classes network accessible. Use a web server to make class definitions available for download.
  4. Start the application. Run the RMI registry, server, and client.

Building a Generic Compute Engine

A compute engine is a remote object on a server that accepts tasks from clients, executes them, and returns results. The compute engine can run tasks whose classes were not defined when the engine was written, leveraging RMI's dynamic code loading capability.

Designing a Remote Interface

The compute engine's remote interface Compute allows tasks to be submitted:

package compute;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Compute extends Remote {
    <T> T executeTask(Task<T> task) throws RemoteException;
}

The Task interface defines the work to be performed:

package compute;

public interface Task<T> {
    T execute();
}

Implementing a Remote Interface

The ComputeEngine class implements the Compute interface and includes a main method to set up the server:

package engine;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import compute.Compute;
import compute.Task;

public class ComputeEngine implements Compute {

    public ComputeEngine() {
        super();
    }

    public <T> T executeTask(Task<T> task) {
        return task.execute();
    }

    public static void main(String[] args) {
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new SecurityManager());
        }
        try {
            String serviceName = "Compute";
            Compute engine = new ComputeEngine();
            Compute stub = (Compute) UnicastRemoteObject.exportObject(engine, 0);
            Registry registry = LocateRegistry.getRegistry();
            registry.rebind(serviceName, stub);
            System.out.println("ComputeEngine bound");
        } catch (Exception e) {
            System.err.println("ComputeEngine exception:");
            e.printStackTrace();
        }
    }
}

Creating a Client Program

The client consists of two classes: ComputePi invokes the Compute object, and Pi implements the Task interface to compute π to a specified precision.

ComputePi client:

package client;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.math.BigDecimal;
import compute.Compute;

public class ComputePi {
    public static void main(String[] args) {
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new SecurityManager());
        }
        try {
            String serviceName = "Compute";
            Registry registry = LocateRegistry.getRegistry(args[0]);
            Compute comp = (Compute) registry.lookup(serviceName);
            Pi task = new Pi(Integer.parseInt(args[1]));
            BigDecimal pi = comp.executeTask(task);
            System.out.println(pi);
        } catch (Exception e) {
            System.err.println("ComputePi exception:");
            e.printStackTrace();
        }
    }
}

Pi task implementation:

package client;

import compute.Task;
import java.io.Serializable;
import java.math.BigDecimal;

public class Pi implements Task<BigDecimal>, Serializable {

    private static final long serialVersionUID = 227L;
    private static final BigDecimal FOUR = BigDecimal.valueOf(4);
    private static final int ROUNDING_MODE = BigDecimal.ROUND_HALF_EVEN;
    private final int digits;

    public Pi(int digits) {
        this.digits = digits;
    }

    public BigDecimal execute() {
        return computePi(digits);
    }

    public static BigDecimal computePi(int digits) {
        int scale = digits + 5;
        BigDecimal arctan1_5 = arctan(5, scale);
        BigDecimal arctan1_239 = arctan(239, scale);
        BigDecimal pi = arctan1_5.multiply(FOUR).subtract(arctan1_239).multiply(FOUR);
        return pi.setScale(digits, BigDecimal.ROUND_HALF_UP);
    }

    public static BigDecimal arctan(int inverseX, int scale) {
        BigDecimal result, numer, term;
        BigDecimal invX = BigDecimal.valueOf(inverseX);
        BigDecimal invX2 = BigDecimal.valueOf(inverseX * inverseX);

        numer = BigDecimal.ONE.divide(invX, scale, ROUNDING_MODE);
        result = numer;
        int i = 1;
        do {
            numer = numer.divide(invX2, scale, ROUNDING_MODE);
            int denom = 2 * i + 1;
            term = numer.divide(BigDecimal.valueOf(denom), scale, ROUNDING_MODE);
            if ((i % 2) != 0) {
                result = result.subtract(term);
            } else {
                result = result.add(term);
            }
            i++;
        } while (term.compareTo(BigDecimal.ZERO) != 0);
        return result;
    }
}

Compiling and Running the Example

Compiling the Example Programs

  1. Build the interface JAR file. Compile the Compute and Task interfaces and package them into a JAR file.
  2. Build server classes. Compile ComputeEngine with the interface JAR in the classpath.
  3. Build client classes. Compile ComputePi and Pi with the interface JAR in the classpath.

Running the Example Programs

  1. Start the RMI registry. Execute rmiregistry (optionally on a specific port).
  2. Start the server. Run ComputeEngine with appropriate system properties for codebase and security policy.
  3. Start the client. Run ComputePi with the server hostname and precision as arguments, specifying codebase and security policy.

Security Features in Java SE

Java's built-in security features protect against malicious programs, ensure data privacy, and verify code provider identities. The JDK provides tools and APIs for access control, digital signatures, and cryptographic services.

Key Components

  • Digital Signatures: Used to verify the authenticity and integrity of code or documents.
  • Certificates: Contain public keys and are signed by a Certificate Authority (CA) to establish trust.
  • Keystores: Password-protected databases storing private keys and associated certificates.

Tools and APIs

  • keytool: Manages keystores, generates key pairs, and handles certificates.
  • jarsigner: Signs and verifies JAR files.
  • Policy Tool: Creates and modifies policy files defining security permissions.
  • Security APIs: Integrate cryptographic services into applications.

Creating a Policy File

Policy files control resource access for applications running under a security manager. Use the Policy Tool to create and edit policy files, granting specific permissions to code from designated sources.

Quick Tour of Controlling Applications

By default, applications run without a security manager, allowing full resource access. To apply security policies, start the JVM with -Djava.security.manager. The security manager enforces permissions defined in policy files, preventing unauthorized actions like file reading or writing.

Steps for Code Signing and Permission Granting

Code Signer's Steps

  1. Create a JAR file. Package class files into a JAR using the jar tool.
  2. Generate keys. Use keytool -genkey to create a key pair and self-signed certificate.
  3. Sign the JAR file. Use jarsigner with the private key to sign the JAR.
  4. Export the certificate. Use keytool -export to provide the public key certificate to the recipient.

Code Receiver's Steps

  1. Import the certificate. Use keytool -import to add the sender's certificate as a trusted entry in the keystore.
  2. Create a policy file entry. Use the Policy Tool to grant permissions to code signed by the imported certificate.
  3. Run the application. Execute the signed JAR under the security manager; the application now has the granted permissions.

File Exchange

For document exchange, sign the document using jarsigner and export the corresponding certificate. The recipient imports the certificate and verifies the signature using jarsigner.

Implementing Custom Permissions

Developers can define custom permission classes to enforce specific security policies tailored to their applications.

Tags: java Error Handling RMI Security Digital Signatures

Posted on Mon, 17 Aug 2026 16:20:14 +0000 by shortj75